基元线程同步——内核模式构造(WaitHandle,EventWaitHandle,AutoResetEvent,ManualResetEvent,Semaphore,Mutex)
2012-08-29 17:46 by xiashengwang, 280 阅读, 0 评论, 收藏, 编辑
一、内核模式构造
内核模式构造,采用的是windows操作系统来同步线程,比用户模式的VolatileRead,VolatileWrite,Interlocked等同步慢很多。但它也有自己的优点:
1,不用像用户模式那样占着cpu“自旋”,浪费cpu资源。
2,内核模式可同步在同一机器不同进程中运行的线程。
3,可实现本地和托管线程相互之间的同步。
4,一个线程可以一直阻塞,直到一个集合中的内核对象全部可用,或部分可用。(WaitAll,WaitAny)
5,阻塞一个线程时,可以指定一个超时值,过了这个超时值就解除阻塞。
二、FCL提供的内核模式构造层次结构
WaitHandle(抽象类)
|——EventWaitHandle
|——AutoResetEvent
|——ManualResetEvent
|——Semaphore
|——Mutex
他们都继承了WaitHandle抽象类,WaitHandle提供了下列共同的静态方法:
WaitOne:阻塞调用线程,直到收到一个信号。
WaitAny:阻塞调用线程,直到收到任意一个信号。
WaitAll:阻塞调用线程,直到收到全部信号。
SingleAndWait:向指定的内核对象发出信号,并等待另一个内核对象收到信号。
Close/Dispose:关闭内核对象句柄。
2.1 EventWaitHandle
它属于事件(event),事件是内核维护的Boolean变量。如果事件为false,在事件上等待的线程就阻塞;如果事件为true,就解除阻塞。它主要有两个方法:
Set:将事件设为true。
ReSet:将事件设为false。
注意:初始化的时候我们可以指定事件的初始值,比如下面的例子就是指定初始时事件为false。
EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);//等同于AutoResetEvent EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset);//等同于ManualResetEvent
2.2 AutoResetEvent
AutoResetEvent是EventWaitHandle的一个简单包装,内部没有额外的任何逻辑。它最大的特点就是,调用了Set方法将事件设为true之后,其中一个等待线程得到执行后,它会自动调用Reset方法,将事件信号设为false,以阻塞其它的线程。相当于放一个线程进来,门自动就关了(自动门)。
例子,使用AutoResetEvent实现一个简单的线程同步锁。
private class SimpleWaitLock : IDisposable { //初始化一定要是true,否者,第一个调用Enter方法的线程会被阻塞 private AutoResetEvent are = new AutoResetEvent(true); #region IDisposable public void Enter() { are.WaitOne();//第一个线程调用这个方法后,事件将会为false,其他线程会被阻塞 Console.WriteLine("thread={0}", Thread.CurrentThread.ManagedThreadId); } public void Exit() { are.Set();//退出时,将事件信号设为true,放一个线程进来后,马上设为false(调用reset) } public void Dispose() { are.Dispose(); } #endregion }
2.3 ManualResetEvent
ManualResetEvent是EventWaitHandle的一个简单包装,内部也没有额外的任何逻辑。它和AutoResetEvent唯一的不同是,调用了Set方法将事件设为true后,不会去调用Reset方法,这将导致事件一直处于true,其它的多个线程都会得到执行,直到你手动调用Reset方法。相当于你把门打开后,需要手动去关(非自动门)。
2.4 Semaphore
信号量(semaphore)是内核维护的一个Int32的变量。信号量为0时,在信号量上等待的线程会阻塞;信号量大于0时,就解除阻塞。它主要有1个方法:
Release():就是一个加1的操作
Release(int32 releasecount):就是一个加releasecount的操作。
初始化semaphore时,可以指定最大和最小信号量的值。
用Sempphore实现同样功能的同步锁:
private class SimpleWaitLock : IDisposable { //初始化指定计数值为1,允许第一个线程可用 private Semaphore sp = new Semaphore(1, 1); #region IDisposable public void Enter() { sp.WaitOne();//第一个线程调用这个方法后,计数值减1,变为0,其他线程会被阻塞 Console.WriteLine("thread={0}", Thread.CurrentThread.ManagedThreadId); } public void Exit() { sp.Release();//计数值加1,其他线程可用 } public void Dispose() { sp.Dispose(); } #endregion }
2.5 Mutex
互斥体(mutex)和计数值为1的Semaphore或AutoResetEvent的工作方式非常相似。这三种方式每次都只释放一个等待的线程。主要方法:
ReleaseMutex():计数减去1
它有一个最大的不同是,它可以在同一线程上循环调用,也就是多次调用WaitOne(),然后在调用等次数的ReleaseMutex()。直到Mutex的计数为0时,其他等待的线程才能被调用。这种方式在平常中可能不太会用到。
可以用Mutex来防止应用程序二次启动,这在平常工作中也经常会碰到。
简单示例:
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); bool createNew; Mutex mutex = new Mutex(false, "ApplicationGuidName", out createNew); //没有启动,就创建一个新的 if (createNew) { Application.Run(new Form1()); } else { // 已经启动了 MessageBox.Show("程序已经启动,不能重复启动!"); } }
注意:也可以用Semaphore和EventWaithandle来实现上面的功能,原理是一样的。但AutoResetEvent和ManualResetEvnet不行,他们没有提供类似的构造方法。
2.6 在一个内核模式变得可用时调用一个方法
可以通过ThreadPool.RegisterWaitForSingleObject方法注册一个方法。当一个事件收到信号,或是指定的时间超时,就会自动调用这个方法。
这个方法对于AutoResetEvent特别有用。但不太适合ManualResetEvent,因为要手动去调用Reset方法,不然会无限的调用这个方法。
private void TestAutoCallBack() { AutoResetEvent mre = new AutoResetEvent(false); //设定超时为2000ms ThreadPool.RegisterWaitForSingleObject(mre, new WaitOrTimerCallback(WaitCallBack), "123", 2000, false); //发出一个信号 mre.Set(); //故意等代3000ms Thread.Sleep(3000); } //有信号或超时就会调用这个方法 private void WaitCallBack(object state, bool timeOut) { Console.WriteLine("is time out = {0}", timeOut); }
运行结果:
is time out = False //得到信号时的调用 is time out = True //超时的调用 is time out = True //超时的调用