故事发生在前几天,我被对象拖着去看房,对于我这种陈年老宅来说,那就是噩梦啊,虽然有诸多不满,但还是去了。出没于各大新旧楼宇之间,看了一天,要到下午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#的了解有所帮助