信号量与互斥体
互斥体(Mutex)是操作系统中一种独占访问共享资源的机制。它像一把所锁,哪个线程获取到互斥体的控制权,则可以访问共享的资源,或者执行处于受保护的代码。而其他的线程如果也想获取控制权,则需要要阻塞等待,知道拥有控制权的线程释放控制权。
信号量(Semaphore)是操作系统中协调多个线程访问共享资源的机制。他内部维护一个非负整数计数器。0表示不能再接受更多的共享访问请求,大于零的数值X表示最多还能接受X个线程的共享访问请求。而非负整数的操作只能通过wait()或者signal()来进行原子操作。signal将计数器加1; wait()将计数器减1,如果当前值为0则阻塞知道有其他线程调用signal。通过这种机制来实现最多X个线程访问共享资源。这种机制通常可以用来解决生产者和消费者问题。(假如产品队列最多能装X个产品,生产者相当于wait操作,消费者相当于signal操作)
.NET 中的WaitHandle
在.Net Framework中有上图中三个子类。用来表示操作系统中的互斥体和信号量。
Mutex
Mutex是对操作系统中互斥体的包装,可以创建未命名的本地Mutex(只在当前进程有效),也可以创建命名的全局Mutex(操作系统有效,跨进程)。具体操作如下:
var _mutex = new Mutex(false); new Task(() => { _mutex.WaitOne();//获取mutex控制权 Console.WriteLine("Got mutex signal. Thread Id:1"); Thread.Sleep(1000); _mutex.ReleaseMutex();//释放mutex控制权 Console.WriteLine("Release mutex signal, Thread Id:1"); }).Start(); new Task(() => { _mutex.WaitOne();//获取mutex控制权,在thread 1释放之前,该调用会阻塞 Console.WriteLine("Got mutex signal Thread id:2"); Thread.Sleep(1000); _mutex.ReleaseMutex(); Console.WriteLine("Release mutex signal, Thread Id:2"); }).Start();
在操作Mutex时,系统会检查Thread Identity,只有获取Mutex控制权的线程才能释放控制权。
Semaphore
Semaphore是对操作系统中信号量的包装,同样也可以创建未命名的Semaphore(只在当前进程有效)和命名的全局Semaphore(操作系统有效,跨进程)具体操作如下:
但是在操作Semaphore时,和Mutex不一样,系统不会检查Thread Identity。任何线程都可以对Semaphore进行WaitOne()和Release()
var _semaphore = new Semaphore(2, 2);//设置最大允许访问共享资源线程数为2 new Task(() => { for (int index = 0; index < 3; index++) { _semaphore.WaitOne();//第三次调用该方法是会被阻塞,因为计数器为0.当其他线程调用Release后才能继续 Console.WriteLine("Got Semaphore Signal. Thread Id:" + Thread.CurrentThread.ManagedThreadId); } }).Start(); new Task(() => { Thread.Sleep(1000); Console.WriteLine("Release Semaphore, Thread Id:" + Thread.CurrentThread.ManagedThreadId); _semaphore.Release();//计数器+1 }).Start();
EventWaitHandle
这个类其实提供了和Mutex类似的线程之间同步,通知机制。它也能创建本地WaitHandle和全局WaitHandle。但是和Mutex又有区别:
- 它内部其实维护了一个布尔类型变量来标示当前wait handle是否空闲:true表示空闲,可以被申请控制权; false则表示wait handle已经被线程持有控制权。(当调用WaitOne获取控制权时,如果内部标量为false,则阻塞知道其他线程将其Set为true.)而对该变量的操作只能通过Set()(将变量设为true),Reset()(将变量设为false)来进行原子操作。
- EventWaitHandle并不提供独占操作,当内部变量为true时,所有线程的WaitOne操作都能获取到控制权,而不是只有一个能获取到(通过设置WaitHandle为AutoReset也可以实现)
- 在操作EventWaitHandle时,系统也不会检查Thread Identity
具体操作如下:
var _waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
//这里指定AutoReset类型后,WaitOne调用获取到控制权后会同时将内部变量置为false,相当于调用Reset() new Task(() => { _waitHandle.WaitOne(); Console.WriteLine("Got the signal, thread id:" + Thread.CurrentThread.ManagedThreadId); }).Start(); new Task(() => { _waitHandle.WaitOne(); Console.WriteLine("Got the signal, thread id:" + Thread.CurrentThread.ManagedThreadId); }).Start(); new Task(() => { Console.WriteLine("Set the signal, thread id:" + Thread.CurrentThread.ManagedThreadId); _waitHandle.Set(); }).Start();