故事發生在前幾天,我被對象拖着去看房,對於我這種陳年老宅來說,那就是噩夢啊,雖然有諸多不滿,但還是去了。出沒於各大新舊樓宇之間,看了一天,要到下午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#的了解有所幫助
