一、内核模式构造
内核模式构造,采用的是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。
代码语言:javascript复制 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实现一个简单的线程同步锁。
代码语言:javascript复制此代码由Java架构师必看网-架构君整理
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时,就解除阻塞。主要方法:
Release():就是一个加1的操作
Release(int32 releasecount):就是一个加releasecount的操作。
初始化semaphore时,可以指定最大和最小信号量的值。
用Sempphore实现同样功能的同步锁:
代码语言:javascript复制 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来防止应用程序二次启动,这在平常工作中也经常会碰到。
简单示例:
代码语言:javascript复制此代码由Java架构师必看网-架构君整理
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方法,不然会无限的调用这个方法。
代码语言:javascript复制 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);
}
运行结果:
代码语言:javascript复制is time out = False //得到信号时的调用
is time out = True //超时的调用
is time out = True //超时的调用