第十三节:实际开发中使用最多的监视锁Monitor、lock语法糖的扩展、混合锁的使用(ManualResetEvent、SemaphoreSlim、ReaderWriterLockSlim)
一. 监视锁(Monitor和lock)
1. Monitor类,限定线程个数的一把锁,两个核心方法:
Enter:锁住某个资源。
Exit:退出某一个资源。
测试案例:开启5个线程同时对一个变量进行自增操作,结果变量有序的输出,说明该锁同时只允许一个线程访问。
但是写法很麻烦,每次都要try-catch-finally,还要声明bool变量。这个时候lock语法糖就很好的解决了这个问题。
代码实践:
1 static object lockMe = new object(); 2 { 3 for (int i = 0; i < 5; i++) 4 { 5 Task.Factory.StartNew(() => 6 { 7 for (int j = 0; j < 100; j++) 8 { 9 var b = false; 10 try 11 { 12 Monitor.Enter(lockMe, ref b); 13 Console.WriteLine(num++); 14 } 15 catch (Exception) 16 { 17 18 throw; 19 } 20 finally 21 { 22 if (b) 23 { 24 Monitor.Exit(lockMe); 25 } 26 } 27 28 } 29 30 }); 31 } 32 }
2. lock语法糖
使用很简单,声明一个静态的object类型变量,调用lock语法糖,将共享变量放入其中,即可保证lock内同时只能一个线程访问。
代码实践:
1 { 2 for (int i = 0; i < 5; i++) 3 { 4 Task.Factory.StartNew(() => 5 { 6 for (int j = 0; j < 100; j++) 7 { 8 lock (lockMe) 9 { 10 Console.WriteLine(num++); 11 } 12 } 13 }); 14 } 15 }
二. 混合锁
1. 简介:混合锁=用户模式锁+内核模式锁,先在用户模式下内旋,如果超过一定的阈值,会切换到内核锁,在内旋模式下,我们会看到大量的Sleep(0),Sleep(1),Yield等语法。
Thread.Sleep(1) 让线程休眠1ms
Thread.Sleep(0) 让线程放弃当前的时间片,让本线程更高或者同等线程得到时间片运行。
Thread.Yield() 让线程立即放弃当前的时间片,可以让更低级别的线程得到运行,当其他thread时间片用完,本thread再度唤醒。
混合锁包括以下三种:ManualResetEventSlim、SemaphoreSlim、ReaderWriterLockSlim,这三种混合锁,要比他们对应的内核模式锁 (ManualResetEvent、Semaphore、ReaderWriterLock),的性能高的多。
2. ManualResetEventSlim
构造函数默认为false,可以使用Wait方法替代WaitOne方法,支持任务取消. (详细的代码同内核版本类似,这里不做测试了)
3. SemaphoreSlim
用法和内核版本类似,使用Wait方法代替WaitOne方法,Release方法不变。(详细的代码同内核版本类似,这里不做测试了)
4. ReaderWriterLockSlim
用法和内核版本类似,但是四个核心方法换成了:
锁读的两个核心方法:EnterReadLock、ExitReadLock。
锁写的两个核心方法:EnterWriteLock、ExitWriteLock。
(详细的代码同内核版本类似,这里不做测试了)