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