多线程数据同步、通信、死锁

  故事发生在前几天,我被对象拖着去看房,对于我这种陈年老宅来说,那就是噩梦啊,虽然有诸多不满,但还是去了。出没于各大新旧楼宇之间,看了一天,要到下午5点左右,终于看好了一个新楼盘,然后看看户型,问问朝向等。都觉得还不错,就准备入手,然后自己还各种科普买房知识,以为准备已充分,合同签了,交了钱,就完事,高高兴兴地就回家了。

  第二天,我对象拿出合同准备再仔细看了看合同内容,然后猛地才发现,合同居然没盖章,这下可把我急着了,这岂不是可以一房多卖,合同都不跟我们盖章,那岂不是合同未生效啊,太气人了,于是赶紧找他们理论,吵吵半天之后盖了章。

  于是乎,我想到了两个问题

  销售员未和我们协商到最后一刻,然后由另一个销售接手(多个线程同时操作同一个数据源)

  合同未完成,就给我们(多线程数据的一致性,lock)

  在多任务系统中,每个独立执行的程序称为进程,也就是正在进行的程序,现在使用的操作系统一般都是多任务的,即能够同时执行多个应用程序,实际情况是,操作系统负责对CPU等设备的资源进行分配和管理,虽然这些设备某一时刻只能做一件事,但以非常小的时间间隔交替执行多个程序,就可以给人以同时执行多个程序的感觉,怎么证明这一点呢?

 1 class TestThread
 2 {
 3     public static void Main(string[] args)
 4     {
 5         Thread t = new Thread(new ThreadStart(new Thread1().Run));
 6         try
 7         {
 8             t.Start();
 9         }
10         catch (Exception ex)
11         {
12             throw ex;
13         }
14 
15         while (true)
16         {
17             Console.WriteLine("主线程正在执行...");
18         }
19     }
20 }
 1 class Thread1
 2 {
 3     public void Run()
 4     {
 5         while (true)
 6         {
 7             Console.WriteLine("子线程正在执行...");
 8         }
 9     }
10 }

执行结果:可以看到操作系统是轮流分配CPU执行主线程和子线程的

 

让一个线程中在执行到lock的代码块时不会切换CPU,而是执行完之后才允许切换CPU到另一个线程,保证数据的完整性

什么情况下会让一房多卖呢,现在我们就用多线程模拟下一房多卖的场景

假设,有100套房子要卖,同时有四个销售员在卖,此时的代码如下:

 1 class TestThread
 2 {
 3     public static void Main(string[] args)
 4     {
 5         // 一房多卖  
 6         Thread t1 = new Thread(new ThreadStart(new Thread2().Run));
 7         t1.Name = "销售员A";
 8         t1.Start();
 9         Thread t2 = new Thread(new ThreadStart(new Thread2().Run));
10         t2.Name = "销售员B";
11         t2.Start();
12         Thread t3 = new Thread(new ThreadStart(new Thread2().Run));
13         t3.Name = "销售员C";
14         t3.Start();
15         Thread t4 = new Thread(new ThreadStart(new Thread2().Run));
16         t4.Name = "销售员D";
17         t4.Start();
18     }
19 }
 1 class Thread2
 2 {
 3     public int house = 100;
 4 
 5     // 重点!!!
 6     // string是引用类型,可以当作lock锁定对象,其他值类型是不能用来lock的
 7     public string lockObj = string.Empty;
 8 
 9     // 会抛出异常 error CS0185: “int”不是 lock 语句要求的引用类型
10     // public int lockInt = 0;
11 
12     public void Run()
13     {
14         while (true)
15         {
16             lock (lockObj)
17             {
18                 if (house > 0)
19                 {
20                     // 重现存在线程不同步,导致的线程安全问题
21                     // 解决办法,加入线程同步锁
22                     Thread.Sleep(10);
23                     Console.WriteLine(Thread.CurrentThread.Name + " 正在卖 " + house + "号房");
24                     house--;
25                 }
26             }
27         }
28     }
29 }

执行结果:同一个房子被卖了三次,原因就在于三个销售员,没有用同一个房源,而且互相之间也没有通信,不知道对方卖了那个房子

 接下来我们就要让四个销售员都卖同一个房源,并且要在销售之前加上锁,防止别人和自己同时在卖一套房子

 1 class TestThread
 2 {
 3     public static void Main(string[] args)
 4     {
 5         // 一房一卖
 6         ThreadStart method = new ThreadStart(new Thread2().Run);
 7         Thread t1 = new Thread(method);
 8         t1.Name = "销售员A";
 9         t1.Start();
10         Thread t2 = new Thread(method);
11         t2.Name = "销售员B";
12         t2.Start();
13         Thread t3 = new Thread(method);
14         t3.Name = "销售员C";
15         t3.Start();
16         Thread t4 = new Thread(method);
17         t4.Name = "销售员D";
18         t4.Start();
19     }
20 }
 1 class Thread2
 2 {
 3     public int house = 100;
 4 
 5     // 重点!!!
 6     // string是引用类型,可以当作lock锁定对象,其他值类型是不能用来lock的
 7     public string lockObj = string.Empty;
 8 
 9     // 会抛出异常 error CS0185: “int”不是 lock 语句要求的引用类型
10     // public int lockInt = 0;
11 
12     public void Run()
13     {
14         while (true)
15         {
16             lock (lockObj)
17             {
18                 if (house > 0)
19                 {
20                     // 重现存在线程不同步,导致的线程安全问题
21                     // 解决办法,加入线程同步锁
22                     Thread.Sleep(10);
23                     Console.WriteLine(Thread.CurrentThread.Name + " 正在卖 " + house + "号房");
24                     house--;
25                 }
26             }
27         }
28     }
29 }

执行结果:房子一房多卖的情况已被解决

 多个线程互相等待对方的锁定标识未被释放,就会产生死锁

 1 class TestThread
 2 {
 3     public static void Main(string[] args)
 4     {
 5         // 重现死锁问题
 6         Thread3 thread3 = new Thread3();
 7         Thread t1 = new Thread(new ThreadStart(thread3.Run));
 8         t1.Name = "销售员A";
 9         t1.Start();
10         Thread.Sleep(1);
11         thread3.lockObj = "中介";
12         Thread t2 = new Thread(new ThreadStart(thread3.Run));
13         t2.Name = "销售员B";
14         t2.Start();
15     }
16 }
 1 class Thread3
 2 {
 3     public string lockObj = string.Empty;
 4     public int house = 100;
 5 
 6     public void Run()
 7     {
 8         if (lockObj == "中介")
 9         {
10             while (true)
11             {
12                 lock (this)
13                 {
14                     if (house > 0)
15                     {
16                         // 重现存在线程不同步,导致的线程安全问题
17                         // 解决办法,加入线程同步锁
18                         Thread.Sleep(10);
19                         // 死锁问题
20                         lock (lockObj) { }
21                         Console.WriteLine(lockObj + "" + Thread.CurrentThread.Name + " 正在卖 " + (house--) + "号房");
22                         ;
23                     }
24                 }
25             }
26         }
27         else
28         {
29             while (true)
30             {
31                 lock (lockObj)
32                 {
33                     if (house > 0)
34                     {
35                         // 重现存在线程不同步,导致的线程安全问题
36                         // 解决办法,加入线程同步锁
37                         Thread.Sleep(10);
38                         // 死锁问题
39                         lock (this) { }
40                         Console.WriteLine("内部的" + Thread.CurrentThread.Name + " 正在卖 " + (house--) + "号房");
41                     }
42                 }
43             }
44         }
45     }
46 }

执行结果:结果显示,多线程已经卡住了,发生了死锁的情况,原因就是双方都等着对方先出售,自己再卖

waitOne 告诉当前线程放弃CPU并进入睡眠状态直到其他线程进入同一个监视器并调用Set为止

多线程之间难免会遇到需要相互打交道的时候,举个例子,开发商为了防止销售员偷懒,就交替为销售员分配客户,下面我们就用代码去模拟下这个场景

 1 class ThreadCommunation
 2 {
 3     public static void Main(string[] args)
 4     {
 5         // 实现销售员A和销售员B交替分配客户
 6         Q q = new Q();
 7 
 8         // 测试多线程之前的通信
 9         new Thread(new ThreadStart(new Producer(q).Run)).Start();
10         new Thread(new ThreadStart(new Customer(q).Run)).Start();
11     }
12 }
 1 /// <summary>
 2 /// DTO
 3 /// </summary>
 4 class Q
 5 {
 6     private EventWaitHandle e = new AutoResetEvent(false);
 7     private readonly object lockA = new object();
 8     private readonly object lockB = new object();
 9     private string seller = "unknown";
10     private string house = "unknown";
11     private bool bFull = false;
12     private bool isStop = false;
13 
14     public Q()
15     {
16 
17     }
18 
19     public Q(string seller, string house)
20     {
21         this.seller = seller;
22         this.house = house;
23     }
24 
25     public void Stop()
26     {
27         this.isStop = true;
28     }
29 
30     public void Put(string seller, string house)
31     {
32         // 重点!!!
33         // 不能用同一个锁定标识,会产生死锁
34         lock (lockA)
35         {
36             if (this.bFull)
37             {
38                 // 等待被另一个线程清空后再插入数据
39                 e.WaitOne();
40             }
41             this.seller = seller;
42             Thread.Sleep(1);
43             this.house = house;
44             this.bFull = true;
45 
46             // 通知等待的线程可以继续执行
47             e.Set();
48         }
49     }
50 
51     public void Get()
52     {
53         if (!this.isStop)
54         {
55             // 重点!!!
56             // 不能用同一个锁定标识,会产生死锁
57             lock (lockB)
58             {
59                 if (!this.bFull)
60                 {
61                     // 等待被另一个线程清空后再插入数据
62                     e.WaitOne();
63                 }
64                 Console.WriteLine(this.ToString());
65                 this.bFull = false;
66 
67                 // 通知等待的线程可以继续执行
68                 e.Set();
69             }
70         }
71     }
72 
73     public override string ToString()
74     {
75         return "销售员:" + this.seller + "正在接待" + this.house + "号客户";
76     }
77 }
 1 /// <summary>
 2 /// 生产者
 3 /// </summary>
 4 class Producer
 5 {
 6     private Q q;
 7 
 8     public Producer(Q q)
 9     {
10         this.q = q;
11     }
12 
13     public void Run()
14     {
15         int i = 0;
16         int house = 100;
17         while (true)
18         {
19             if (house > 0)
20             {
21                 if (i == 0)
22                 {
23                     q.Put("销售员A", (house--).ToString());
24                 }
25                 else
26                 {
27                     q.Put("销售员B", (house--).ToString());
28                 }
29 
30                 i = (i + 1) % 2;
31             }
32             else
33             {
34                 q.Stop();
35             }
36         }
37     }
38 }
 1 /// <summary>
 2 /// 消费者
 3 /// </summary>
 4 class Customer
 5 {
 6     private Q q;
 7 
 8     public Customer(Q q)
 9     {
10         this.q = q;
11     }
12 
13     public void Run()
14     {
15         while (true)
16         {
17             q.Get();
18         }
19     }
20 }

执行结果:可以看出销售员A和销售员B正在交替接待客户

总结:理论不是太多,全是实践而来的干货,希望对大家对C#的了解有所帮助

posted @ 2019-09-16 16:15  技术宅的化身  阅读(707)  评论(0编辑  收藏  举报