线程也疯狂-----线程同步(2)
内核模式构造
前篇我们已经提过,内核模式构造比用户模式构造慢很多,一个原因是它们要求Windows操作系统自身的配合,另一个原因是内核对象上调用的每个方法都造成调用线程从托管代码转换为本机用户模式代码,再转换为背景内核模式代码,这些转换需要大量的CPU时间。
但是内核模式拥有用户模式没有的优点:
1. 当检测到资源竞争时,windows会阻塞输掉的线程
2. 可同步一台机器中不同进程中运行的线程
3. 防止未经授权的账户访问线程
4. 阻塞的线程可以指定超时值
事件和信号量就是两种内核模式线程同步构造。
WaitHandle抽象基类,唯一的作用就是包装一个windows内核对象句柄,继承WaitHadle的对象可以分为三类:1. 事件构造(AutoResetEvent、ManualResetEvent) 2. Semaphore构造 3. Mutex构造
事件构造
AutoResetEvent、ManualResetEvent:其基本工作原理是多个线程持有同一个XXXResetEvent,在这个XXXResetEvent未被set前,各线程都在WaitOne()除挂起;在这个XXXResetEvent被set后,所有被挂起的线程中有一个(AutoResetEvent的情况下)或全部(ManualResetEvent的情况下)恢复执行。
AutoResetEvent举例:
1 AutoResetEvent autoResetEvent = new AutoResetEvent(true); //true默认执行第一个未挂起的线程 2 3 Task.Factory.StartNew(() => 4 { 5 Console.WriteLine("线程1:我已经开始任务已经执行,等待指示"); 6 autoResetEvent.WaitOne(); 7 Console.WriteLine("线程1任务执行结束"); 8 autoResetEvent.Set(); 9 }); 10 11 12 Task.Factory.StartNew(() => 13 { 14 Console.WriteLine("线程2:我已经开始任务已经执行,等待指示"); 15 // Thread.Sleep(1000); 16 autoResetEvent.WaitOne(); 17 Console.WriteLine("线程2任务执行结束");; 18 }); 19 Console.WriteLine("程序结束"); 20 Console.ReadLine();
ManualResetEvent举例:
1 ManualResetEvent autoResetEvent = new ManualResetEvent(false); //true默认执行第一个未挂起的线程 2 3 Task.Factory.StartNew(() => 4 { 5 Console.WriteLine("线程1:我已经开始任务已经执行,等待指示"); 6 autoResetEvent.WaitOne(); 7 Console.WriteLine("线程1任务执行结束"); 8 9 }); 10 Task.Factory.StartNew(() => 11 { 12 Console.WriteLine("线程2:我已经开始任务已经执行,等待指示"); 13 // Thread.Sleep(1000); 14 autoResetEvent.WaitOne(); 15 Console.WriteLine("线程2任务执行结束");; 16 }); 17 Console.WriteLine("1s后再通知线程执行任务"); 18 Thread.Sleep(1000); 19 autoResetEvent.Set(); 20 Console.WriteLine("程序结束"); 21 Console.ReadLine();
记得有一个面试题,创建四个线程并且按照顺序打印ABCD重复三次,就考得是线程同步的问题,大家可以思考一下怎么解决。
Semaphore构造
构造原理:通过内核维护int32变量,信号量为0时,在信号量上等待的线程会阻塞,信号量大于0时,解除阻塞,在信号量上等待的线程接触阻塞时,内核自动从信号量的计数中减1.
1 Semaphore semaphore = new Semaphore(0, Int32.MaxValue); //指定最大并发 2 3 Task.Factory.StartNew(() => 4 { 5 Console.WriteLine("线程1:我已经开始任务已经执行,等待指示"); 6 semaphore.WaitOne(); 7 Console.WriteLine("线程1任务执行结束"); 8 semaphore.Release(); 9 10 }); 11 Task.Factory.StartNew(() => 12 { 13 Console.WriteLine("线程2:我已经开始任务已经执行,等待指示"); 14 // Thread.Sleep(1000); 15 semaphore.WaitOne(); 16 Console.WriteLine("线程2任务执行结束");; 17 }); 18 Console.WriteLine("1s后再通知线程执行任务"); 19 semaphore.Release(); 20 Console.WriteLine("程序结束"); 21 Console.ReadLine();
Mutex构造
原理同AtuoResetEvent相似,都是一次只能释放一个正在等待的线程,但是互斥有一些额外的逻辑,这使得它比先前的构造更复杂。首先,Mutex对象会查询调用线程的Int32 ID,记录是哪一个线程获得了它,一个线程调用ReleaseMutex时,mutex确保调用线程就是获取Mutex的那个线程,不然mutex对象的状态就会发生改变,另外,Mutex对象维护着一个递归计数,指出拥有该Mutex的线程拥有了它多少次,如果一个线程当前拥有一个mutex,而后该线程再次在mutex上等待,计数就会递增,这个线程允许继续运行。
构造Mutex的递归锁:
1 class MutexLock : IDisposable 2 { 3 private readonly Mutex mutex; 4 5 public MutexLock() 6 { 7 mutex = new Mutex(); 8 } 9 10 public void Enter() 11 { 12 mutex.WaitOne(); 13 //随便坐任何事情 14 Leave(); 15 mutex.ReleaseMutex(); 16 17 } 18 19 public void Leave() 20 { 21 mutex.WaitOne(); 22 //随便坐任何事情 23 mutex.ReleaseMutex(); 24 } 25 26 public void Dispose() 27 { 28 mutex.Dispose(); 29 } 30 }