C# 多線程學習(五)線程同步和沖突解決


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)”通知它生產已經完成,此后消費者進入消費狀態。循環往復,效果如下: 
這里寫圖片描述 
差不多如此吧,上面的方法已經可以幫助我們解決多線程中可能出現的大部分問題,剩下的就不再介紹了,同步的處理也到此結束了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM