from:https://blog.csdn.net/codedoctor/article/details/74358257
首先先說一個線程不同步的例子吧,以下為售票員的模擬售票,多個售票員出售100張門票,代碼如下:
using System; using System.Text; using System.Collections.Generic; using System.Threading; namespace threadTest { class Program { class ThreadLock { private Thread thread_1; private Thread thread_2; private List<int> tickets; private object objLock = new object();//對象鎖的對象 public ThreadLock() { thread_1 = new Thread(Run); thread_1.Name = "Sailer_1"; thread_2 = new Thread(Run); thread_2.Name = "Sailer_2"; } public void Start() { tickets = new List<int>(100); for(int i = 1; i <= 100; i++) { tickets.Add(i); } thread_1.Start(); thread_2.Start(); } public void Run() { while (tickets.Count > 0) { int get = tickets[0]; Console.WriteLine("{0} sail a ticket ,ticket number :{1} ", Thread.CurrentThread.Name, get.ToString()); tickets.RemoveAt(0); Thread.Sleep(1); } } } static void Main() { ThreadLock TK = new ThreadLock(); TK.Start(); Console.ReadKey(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
以上為一個模擬售票系統,兩個線程對一個票箱進行操作,每次都取出最上層的票,然后輸出,運行之后查看結果會發現在在同一張票上,兩個線程都可能同時賣出,如下: 
出現以上的情況的原因就是在多線程的的情況之下,線程的執行順序是不可控的,就可能會出現以上的情況,具體原因可能如下:
請看代碼:
int get = tickets[0]; Console.WriteLine("{0} sail a ticket ,ticket number :{1} ", Thread.CurrentThread.Name, get.ToString()); tickets.RemoveAt(0);
- 1
- 2
- 3
- 4
比如,線程A在剛從tickets中確定要取最底下的一張票之后還未將這張票輸出並刪除,這時候線程A被分配的CPU時間就用光了。
然后輪到另一個線程B執行,線程B的時間充足,也同樣確認了線程A剛才確定的那張票,然后取出了那張票,取出然后輸出並刪除掉那張票,然后將CPU控制權交到了線程A上。
又輪到了線程A執行,線程A由於剛才已經確定了選定的票號,所以直接輸出了那個票號,然后將最底下的票刪除。所以可以看到取票有一段是跳躍着取得,如:1,3,5,7,…
線程同步
出現這種情況的原因就是多個線程都是對同一個資源進行操作所致,所以在多線程編程應盡可能避免這種情況,當然有些情況下確實避免不了這種情況,這就需要對其采用一些手段來確保不會出現這種情況,這就是所謂的線程的同步。
在C#中實現線程的同步有幾種方法:lock、Mutex、Monitor、Semaphore、Interlocked和ReaderWriterLock等。同步策略也可以分為同步上下文、同步代碼區、手動同步幾種方式。
Lock同步
針對上面的代碼,要保證不會出現混亂的情況,可以用lock關鍵字來實現,出現問題的部分就是在於判斷剩余票數是否大於0,如果大於0則從當前總票數中減去最大的一張票,因此可以對這部分進行lock處理,代碼如下:
public void Run() { while (tickets.Count > 0) { lock (objLock) { if (tickets.Count > 0) { int get = tickets[0]; Console.WriteLine("{0} sail a ticket ,ticket number :{1} ", Thread.CurrentThread.Name, get.ToString()); tickets.RemoveAt(0); Thread.Sleep(1); } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
這樣處理之后,這個售票系統就變得正常了,效果如下:
總的來說,lock語句是一種有效的、不跨越多個方法的小代碼塊同步的做法,也就是使用lock語句只能在某個方法的部分代碼之間,不能跨越方法。
Monitor類
針對以上的處理方法,我們用Monitor類來處理的話是如下代碼:
public void Run()
{
while (tickets.Count > 0) { Monitor.Enter(objLock); if (tickets.Count > 0) { int get = tickets[0]; Console.WriteLine("{0} sail a ticket ,ticket number :{1} ", Thread.CurrentThread.Name, get.ToString()); tickets.RemoveAt(0); Thread.Sleep(1); } } Monitor.Exit(objLock); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
運行可以知道,這段代碼和lock方法的結果是一樣的,當然其實lock就是用Monitor類實現的,除了鎖定代碼區,我們還可用Monitor類的Wait()和 pulse()方法。
Wait()方法是臨時釋放當前活得的鎖,並使當前對象處於阻塞狀態
Pulse()方法是通知處於等待狀態的對象可以准備就緒了,它一會就會釋放鎖。
下面我們來實現一個生產者和消費者模式,生產者線程負責生產數據,消費者線程將生產者生產出來的數據輸出,代碼如下:
using System; using System.Text; using System.Collections.Generic; using System.Threading; namespace threadTest { class Program { public class Cell { int cellContents; // Cell對象里邊的內容 bool readerFlag = false; // 狀態標志,為true時可以讀取,為false則正在寫入 public int ReadFromCell() { lock (this) // Lock關鍵字保證了當前代碼塊在同一時間只允許一個線程進入執行 { if (!readerFlag)//如果現在不可讀取 { try { //等待WriteToCell方法中調用Monitor.Pulse()方法將這個線程喚醒 Monitor.Wait(this); } catch (SynchronizationLockException e) { Console.WriteLine(e); } } Console.WriteLine("Use: {0}", cellContents); readerFlag = false; //重置readerFlag標志,表示消費行為已經完成 Monitor.Pulse(this); //通知WriteToCell()方法(該方法在另外一個線程中執行,等待中) } return cellContents; } public void WriteToCell(int n) { lock (this) { if (readerFlag) { try { Monitor.Wait(this); } catch (SynchronizationLockException e) { //當同步方法(指Monitor類除Enter之外的方法)在非同步的代碼區被調用 Console.WriteLine(e); } } cellContents = n; Console.WriteLine("Produce: {0}", cellContents); readerFlag = true; Monitor.Pulse(this); //通知另外一個線程中正在等待的ReadFromCell()方法 } } } public class CellProd { Cell cell; // 被操作的Cell對象 int quantity = 1; // 生產者生產次數,初始化為1 public CellProd(Cell box, int request) { cell = box; quantity = request; } public void ThreadRun() { for (int looper = 1; looper <= quantity; looper++) cell.WriteToCell(looper); //生產者向操作對象寫入信息 } } public class CellCons { Cell cell; int quantity = 1; public CellCons(Cell box, int request) { //構造函數 cell = box; quantity = request; } public void ThreadRun() { int valReturned; for (int looper = 1; looper <= quantity; looper++) valReturned = cell.ReadFromCell();//消費者從操作對象中讀取信息 } } public static void Main(String[] args) { int result = 0; //一個標志位,如果是0表示程序沒有出錯,如果是1表明有錯誤發生 Cell cell = new Cell(); //下面使用cell初始化CellProd和CellCons兩個類,生產和消費次數均為20次 CellProd prod = new CellProd(cell, 20); CellCons cons = new CellCons(cell, 20); Thread producer = new Thread(new ThreadStart(prod.ThreadRun)); Thread consumer = new Thread(new ThreadStart(cons.ThreadRun)); //生產者線程和消費者線程都已經被創建,但是沒有開始執行 try { producer.Start(); consumer.Start(); producer.Join(); consumer.Join(); Console.ReadLine(); } catch (ThreadStateException e) { //當線程因為所處狀態的原因而不能執行被請求的操作 Console.WriteLine(e); result = 1; } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
這個例程中,生產者線程和消費者線程是交替進行的,生產者寫入一個數,消費者立即讀取並輸出。
同步是通過等待Monitor.Pulse()來完成的。
首先生產者生產了一個數據,而同一時刻消費者處於等待狀態,直到收到生產者的“脈沖(Pulse)”通知它生產已經完成,此后消費者進入消費狀態。循環往復,效果如下:
差不多如此吧,上面的方法已經可以幫助我們解決多線程中可能出現的大部分問題,剩下的就不再介紹了,同步的處理也到此結束了。
