第十二节:深究内核模式锁的使用场景(自动事件锁、手动事件锁、信号量、互斥锁、读写锁、动态锁)

一. 整体介绍

温馨提示:内核模式锁,在不到万不得已的情况下,不要使用它,因为代价太大了,有很多种替代方案。

  内核模式锁包括:

    ①:事件锁

    ②:信号量

    ③:互斥锁

    ④:读写锁

    ⑤:动态锁

 

二. 事件锁

 事件锁包括:

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         }

 

 

 

 

 

 

posted @ 2018-01-17 15:14  Yaopengfei  阅读(1406)  评论(4编辑  收藏  举报