.Net中信号量的使用
信号量的使用类,在System.Threading下,主要有三种,Semaphore,ManualResetEvent,CountdownEvent,下面我一一介绍这三个类的作用:
一、Semaphore类
Semaphore的主要作用是控制并发,初始设置时,需要设定初始入口数,和最大并发入口数。这个型号量就像是一种排队,就是说,N个线程通过WaitOne进队,又通过Realse出队这么一个概念。
最常用的地方,无锁线程同步,Http请求并发控制等等。
写一个简单的线程锁的例子:
Semaphore lock=new Semaphore(1,1);//意思是设置最大并发只有1,且初始入口只有1的信号量
try
{
lock.WaitOne();
//TODO:写需要线程同步代码的区域
}
finally
{
lock.Realse();//通过finally出锁,这样避免在return或者出现异常的时候漏掉Realse,导致线程卡死
}
二、ManualResetEvent 类
ManualResetEvent是单信号控制的,WaitOne等待信号,Set去发送信号。
new ManualResetEvent(false)//表示当前没有信号
new ManualResetEvent(true)//表示当前有信号
这个信号量场景应用非常丰富,下面举一下例子:
控制循环线程:
Thread thread=new Thread(Run);
ManualResetEvent close=new ManualResetEvent(false);
private void Run()
{
while(!close.WaitOne(20))
{
//因为close在没有关闭之前没有信号,所以WaitOne(20),就是在等待20毫秒之后返回false
//那么这样写就可以达到,每隔20毫秒运行一下while里面的代码的效果
}
}
public void Close()
{
close.Set();//当要关闭的时候,设置close为有信号状态
thread.Join();//等待线程关闭,因为close已设置成有信号状态,所以必定跳出while循环,那么Join肯定可以完全结束线程
}
网页利用Ajax等待事件触发:
我一直觉得这是一个很棒的想法,网页中在未知的情况下等待某个用户操作完成是一个很头疼的事情,一般要通过轮询解决,比如支付,现在可以利用信号量来完成这个事情。
最大的利用点,就是Ajax是异步请求,这就相当于创建了一个线程,可以在完成支付的时候触发一个Http请求,然后服务器对这个Http请求处理是,找到对应的信号量,先将数据存入内存,然后触发它,
信号量被触发之后,WaitOne就结束了,然后拿到内存中的数据并返回。
可以这么做,下面我简单的写一下代码,实际也证明可行:
public class TimerHandler { public Guid Id { get; set; } /// <summary> /// 超时时间 /// </summary> public int TimeOut { get; set; } /// <summary> /// 是否已被使用 /// </summary> public bool IsUsed { get; set; } /// <summary> /// 信号量是否已经被释放 /// </summary> public bool IsDisposed { get; set; } /// <summary> /// 数据 /// </summary> public string MemoryData { get; set; } public ManualResetEvent WaitHandle { get; set; } } public class TimerManager { /// <summary> /// 单例 /// </summary> private static readonly TimerManager _manager = new TimerManager(); /// <summary> /// 线程同步控制信号量 /// </summary> private readonly Semaphore _lock = new Semaphore(1, 1); /// <summary> /// 储存信号量的一个字典结构 /// </summary> private Dictionary<Guid, TimerHandler> _timerDic = new Dictionary<Guid, TimerHandler>(); /// <summary> /// 当前实例 /// </summary> public static TimerManager Current { get { return _manager; } } /// <summary> /// 创建触发事件的句柄,也就是一个Id值 /// </summary> /// <param name="timeOut">设置等待的超时时间</param> /// <returns></returns> public Guid CreateEvent(int timeOut) { try { if (timeOut <0 || timeOut > 3600000) { timeOut = 3600000; } _lock.WaitOne(); TimerHandler handler = new TimerHandler(); handler.Id = Guid.NewGuid(); handler.TimeOut = timeOut; handler.WaitHandle = new ManualResetEvent(false); if (!_timerDic.ContainsKey(handler.Id)) { _timerDic.Add(handler.Id, handler); } return handler.Id; } finally { _lock.Release(); } } /// <summary> /// 等待事件触发 /// </summary> /// <param name="eventId"></param> /// <param name="res"></param> /// <returns></returns> public bool WaitEvent(Guid eventId,out string res) { res = null; TimerHandler handle = null; try { _lock.WaitOne(); handle = _timerDic[eventId]; } finally { _lock.Release(); } if (handle != null) { lock (handle) { handle.IsUsed = true; } var r=handle.WaitHandle.WaitOne(handle.TimeOut); try { _lock.WaitOne(); _timerDic.Remove(handle.Id); } finally { _lock.Release(); } lock (handle) { if (!handle.IsDisposed) { handle.IsDisposed = true; handle.WaitHandle.Close(); handle.WaitHandle.Dispose(); } } if (r) { res = handle.MemoryData; } return r; } return false; } /// <summary> /// 触发事件 /// </summary> /// <param name="eventId"></param> /// <param name="json"></param> public void RaiseEvent(Guid eventId,string json) { TimerHandler handle = null; try { _lock.WaitOne(); handle = _timerDic[eventId]; } finally { _lock.Release(); } lock (handle) { if (!handle.IsDisposed) { //如果等待超时了IsDisposed为true, //此时信号量已经释放了,不用Set,否则会报错 handle.MemoryData = json; handle.WaitHandle.Set(); } } } }
然后设置Http的三个接口,分别调用,CreateEvent,WaitEvent,RaiseEvent(省了点事,以下就简要写了,实际用到的代码是有,但不做展示):
js上这么写:
function GetCallBackUrl(eventId) {
//返回能触发RaiseEvent请求的链接 return "http://***/RaiseEvent?id=" + eventId; } //event_fun 获取回调函数Id之后的处理 //callbackFunction 获得回调数据之后的处理,可能存在超时 //timeout 超时时间 function BeginAsCallBack(event_fun,callbackFunction,timeout) {
//先调用CreateEvent获取EventId $.post("http://***/CreateEvent", timeout, function (res) { eventId = res.EventId;
//然后调用WaitEvent等待请求,一直等待,因为是异步,所以依然可以执行下面的js代码 //先用ajax执行等待 $.post("http://***/WaitEvent", JSON.stringify({ EventId: eventId }), callbackFunction, "json"); //然后触发支付 event_fun(eventId); }, "json"); } //执行支付 function DoPay() { BeginAsCallBack(function (eventId) { var req = { CallBackUrl:GetCallBackUrl(eventId) //触发RaiseEvent请求的Url
//支付请求参数 }; //请求支付 $.post("http://***/Pay", JSON.stringify(req), function (result, status, xhr) { // }, "json"); }, function (res_callBack) { if (res_callBack.IsTimeOut) { alert("回调超时"); } else { var data=res_callBack.Data;//回调数据 } }, 20000); }
这样就能达到支付完成之后,同时原网页上会有提示信息的效果。
三、CountdownEvent类
CountdownEvent类,在值减少到0的时候,发出信号,主要的用途在于触发了多个线程,这些线程是未知数量的,需要等待这些线程同时完成。
通常用于并发遍历,并发搜索等场景,方法简单,消耗内存较少。
浙公网安备 33010602011771号