第十二节:深究内核模式锁的使用场景(自动事件锁、手动事件锁、信号量、互斥锁、读写锁、动态锁)
一. 整体介绍
温馨提示:内核模式锁,在不到万不得已的情况下,不要使用它,因为代价太大了,有很多种替代方案。
内核模式锁包括:
①:事件锁
②:信号量
③:互斥锁
④:读写锁
⑤:动态锁
二. 事件锁
事件锁包括:
A. 自动事件锁(AutoResetEvent)
使用场景:可以用此锁实现多线程环境下某个变量的自增.
现实场景: 进站火车闸机,我们用火车票来实现进站操作.
true: 表示终止状态,闸机中没有火车票
false: 表示费终止状态,闸机中此时有一张火车票
B.手动事件锁(ManualResetEvent)
现实场景:有人看守的铁道栅栏(和自动事件锁不一样,不能混用)
true: 栅栏没有合围,没有阻止行人通过铁路
false:栅栏合围了, 阻止行人通过
* 下面案例发现,锁不住,自增仍然是无序的输出了.
* 核心方法:WaitOne和Set
代码实践-自动事件锁:
1 static AutoResetEvent autoResetLock1 = new AutoResetEvent(true); 2 static AutoResetEvent autoResetLock2 = new AutoResetEvent(false); 3 static int num2 = 0; 4 { 5 //1. 能输出 6 { 7 autoResetLock1.WaitOne(); 8 Console.WriteLine("autoResetLock1检验通过,可以通行"); 9 autoResetLock1.Set(); 10 } 11 12 //2. 不能输出 13 { 14 autoResetLock2.WaitOne(); 15 Console.WriteLine("autoResetLock2检验通过,可以通行"); 16 autoResetLock2.Set(); 17 } 18 19 //3.下面代码的结果:num从0-249,有序的发现可以锁住。 20 { 21 for (int i = 0; i < 5; i++) 22 { 23 Task.Factory.StartNew(() => 24 { 25 for (int j = 0; j < 50; j++) 26 { 27 try 28 { 29 autoResetLock1.WaitOne(); 30 Console.WriteLine(num2++); 31 autoResetLock1.Set(); 32 } 33 catch (Exception ex) 34 { 35 Console.WriteLine(ex.Message); 36 } 37 38 } 39 }); 40 } 41 } 42 }
代码实践-手动事件锁:
1 static int num2 = 0; 2 static ManualResetEvent mreLock = new ManualResetEvent(true); 3 //下面代码锁不住,仍然是无序的输出了 4 { 5 for (int i = 0; i < 5; i++) 6 { 7 Task.Factory.StartNew(() => 8 { 9 for (int j = 0; j < 50; j++) 10 { 11 try 12 { 13 mreLock.WaitOne(); 14 Console.WriteLine(num2++); 15 mreLock.Set(); 16 } 17 catch (Exception ex) 18 { 19 Console.WriteLine(ex.Message); 20 } 21 22 } 23 }); 24 } 25 }
三. 信号量
信号量:
* 核心类:Semaphore,通过int数值来控制线程个数。
* 通过观察构造函数 public Semaphore(int initialCount, int maximumCount);:
* initialCount: 可以同时授予的信号量的初始请求数。
* maximumCount: 可以同时授予的信号量的最大请求数。
* static Semaphore seLock = new Semaphore(1, 1); //表示只允许一个线程通过
* 下面的案例可以有序的输出。
* 核心方法:WaitOne和Release
代码实践:
1 static Semaphore seLock = new Semaphore(1, 1); //只允许一个线程通过
2 //下面代码锁住了,可以有序的输出 3 { 4 for (int i = 0; i < 5; i++) 5 { 6 Task.Factory.StartNew(() => 7 { 8 for (int j = 0; j < 50; j++) 9 { 10 try 11 { 12 seLock.WaitOne(); 13 Console.WriteLine(num2++); 14 seLock.Release(); 15 } 16 catch (Exception ex) 17 { 18 Console.WriteLine(ex.Message); 19 } 20 21 } 22 }); 23 } 24 }
四. 互斥锁
互斥锁:
核心方法:WaitOne和ReleaseMutex
下面案例可以锁住,有序输出
总结以上三种类型的锁,都有一个WaitOne方法,观察源码可知,都继承于WaitHandle类。
代码实践:
1 static Mutex mutex = new Mutex(); 2 //下面代码锁住了,可以有序的输出 3 { 4 for (int i = 0; i < 5; i++) 5 { 6 Task.Factory.StartNew(() => 7 { 8 for (int j = 0; j < 50; j++) 9 { 10 try 11 { 12 mutex.WaitOne(); 13 Console.WriteLine(num2++); 14 mutex.ReleaseMutex(); 15 } 16 catch (Exception ex) 17 { 18 Console.WriteLine(ex.Message); 19 } 20 21 } 22 }); 23 } 24 }
五. 读写锁
读写锁(ReaderWriterLock):
背景:多个线程读,一个线程写,如果写入的时间太久,此时读的线程会被卡死,这个时候就要用到读写锁了。
锁读的两个核心方法:AcquireReaderLock和ReleaseReaderLock。
锁写的两个核心方法:AcquireWriterLock和ReleaseWriterLock。
代码实践:
1 static ReaderWriterLock rwlock = new ReaderWriterLock(); 2 private void button24_Click(object sender, EventArgs e) 3 { 4 #region 01-读写锁 5 { 6 //开启5个线程执行读操作 7 for (int i = 0; i < 5; i++) 8 { 9 Task.Run(() => 10 { 11 Read(); 12 }); 13 } 14 //开启1个线程执行写操作 15 Task.Factory.StartNew(() => 16 { 17 Write(); 18 }); 19 } 20 #endregion 21 22 } 23 /// <summary> 24 /// 线程读 25 /// </summary> 26 static void Read() 27 { 28 while (true) 29 { 30 Thread.Sleep(10); 31 rwlock.AcquireReaderLock(int.MaxValue); 32 Console.WriteLine("当前 t={0} 进行读取 {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 33 rwlock.ReleaseReaderLock(); 34 } 35 } 36 /// <summary> 37 /// 线程写 38 /// </summary> 39 static void Write() 40 { 41 while (true) 42 { 43 Thread.Sleep(300); 44 rwlock.AcquireWriterLock(int.MaxValue); 45 Console.WriteLine("当前 t={0} 进行写入 {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 46 rwlock.ReleaseWriterLock(); 47 } 48 }
六. 动态锁
动态锁(CountdownEvent):
* 作用:限制线程数的一个机制。
* 业务场景:有Orders、Products、Users表,我们需要多个线程从某一张表中读取数据。
* 比如:Order表10w,10个线程读取。(每个线程读1w)
Product表5w,5个线程读取。(每个线程读1w)
User表2w,2个线程读取。(每个线程读1w)
三个核心方法:
①.Reset方法:重置当前的线程数量上限。(初始化的时候,默认设置一个上限)
②.Signal方法:将当前的线程数量执行减1操作。(使用一个thread,这个线程数量就会减1操作,直到为0后,继续下一步)
③.Wait方法:相当于我们的Task.WaitAll方法。
代码实践:
1 //初始化线程数量上限为10. 2 static CountdownEvent cdLock = new CountdownEvent(10); 3 private void button25_Click(object sender, EventArgs e) 4 { 5 //加载Orders搞定 6 cdLock.Reset(10); 7 for (int i = 0; i < 10; i++) 8 { 9 Task.Factory.StartNew(() => 10 { 11 LoadOrder(); 12 }); 13 } 14 cdLock.Wait(); 15 Console.WriteLine("所有的Orders都加载完毕。。。。。。。。。。。。。。。。。。。。。"); 16 17 //加载Product搞定 18 cdLock.Reset(5); 19 for (int i = 0; i < 5; i++) 20 { 21 Task.Run(() => 22 { 23 LoadProduct(); 24 }); 25 } 26 cdLock.Wait(); 27 Console.WriteLine("所有的Products都加载完毕。。。。。。。。。。。。。。。。。。。。。"); 28 29 //加载Users搞定 30 cdLock.Reset(2); 31 for (int i = 0; i < 2; i++) 32 { 33 Task.Factory.StartNew(() => 34 { 35 LoadUser(); 36 }); 37 } 38 cdLock.Wait(); 39 Console.WriteLine("所有的Users都加载完毕。。。。。。。。。。。。。。。。。。。。。"); 40 41 Console.WriteLine("所有的表数据都执行结束了。。。恭喜恭喜。。。。"); 42 Console.Read(); 43 } 44 static void LoadOrder() 45 { 46 //书写具体的业务逻辑 47 Console.WriteLine("当前LoadOrder正在加载中。。。{0}", Thread.CurrentThread.ManagedThreadId); 48 //线程数量减1 49 cdLock.Signal(); 50 51 } 52 static void LoadProduct() 53 { 54 //书写具体的业务逻辑 55 Console.WriteLine("当前LoadProduct正在加载中。。。{0}", Thread.CurrentThread.ManagedThreadId); 56 //线程数量减1 57 cdLock.Signal(); 58 } 59 static void LoadUser() 60 { 61 //书写具体的业务逻辑 62 Console.WriteLine("当前LoadUser正在加载中。。。{0}", Thread.CurrentThread.ManagedThreadId); 63 //线程数量减1 64 cdLock.Signal(); 65 }