1. 程式人生 > >C# 多線程學習(五)線程同步和沖突解決

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

執行 負責 void RF 運行 ner 有效 als 效果

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# 多線程學習(五)線程同步和沖突解決