1. 程式人生 > 實用技巧 >C#執行緒同步方式

C#執行緒同步方式

一、程序內部的執行緒同步

1、使用lock,用法如下:

        private static readonly object SeqLock = new object();

        private void Print()
        {
            lock (SeqLock)
            {
                Console.WriteLine("test");
            }
        }

特性:只能傳遞物件,無法設定等待超時

2、使用:InterLocked(原子操作)

其在System.Threading名稱空間下,Interlocked實際是類控制計數器,從而實現程序的同步,其很容易實現生產者消費者模型

 //緩衝區,只能容納一個字元
      private static char buffer;
      //標識量(緩衝區中已使用的空間,初始值為0)
      private static long numberOfUsedSpace = 0;
      static void Main(string[] args)
      {
        //執行緒:寫入者
       Thread Writer = new Thread(delegate ()
       {
         string str = "這裡面的字會一個一個讀取出來,一個都不會少,,,";
         for
(int i = 0; i < 24; i++) { //寫入資料前檢查緩衝區是否已滿 //如果已滿,就進行等待,直到緩衝區中的資料被程序Reader讀取為止 while (Interlocked.Read(ref numberOfUsedSpace) == 1) { Thread.Sleep(50); } buffer = str[i]; //向緩衝區寫入資料 //寫入資料後把緩衝區標記為滿(由0變為1) Interlocked.Increment(ref
numberOfUsedSpace); } }); //執行緒:讀出者 Thread Reader = new Thread(delegate () { for (int i = 0; i < 24; i++) { //讀取資料前檢查緩衝區是否為空 //如果為空,就進行等待,直到程序Writer向緩衝區中寫入資料為止 while (Interlocked.Read(ref numberOfUsedSpace) == 0) { Thread.Sleep(50); } char ch = buffer; //從緩衝區讀取資料 Console.Write(ch); Interlocked.Decrement(ref numberOfUsedSpace); } }); //啟動執行緒 Writer.Start(); Reader.Start(); Console.ReadKey();

3、使用Monitor

其中Monitor.Enter()和lock相同

            Monitor.Enter(obj){
                //Synchronized part
            }finally{
                Monitor.Exit(obj);
            }

TryEnter則可設定等待時間等

            bool lockTaken=false;
            Monitor.TryEnter(obj, 500, ref lockTaken);
            if(lockTaken){
                try
                {
                    //Synchronized part
                }
                finally
                {
                    Monitor.Exit(obj);
                }
            }else{
                //don't aquire the lock, excute other parts
            }

二、程序間的同步

1. WaitHandle:

封裝等待對共享資源進行獨佔訪問的作業系統特定的物件。WaitHandle:是一個抽象類,我們一般不直接用,而是用它的派生類:

AutoResetEvent、EventWaitHandle、ManualResetEvent、Mutex、Semaphore

這個抽象類的方法如下:

WaitOne(): 等待一個訊號的出現,可設定超時;

WaitAll(): 等待多個訊號的出現,可設定超時;

WaitAny(): 等待任意一個訊號的出現,可設定超時;

2、Mutex:與Monitor 類似,只有一個執行緒能夠獲取鎖定。利用WaitOne() 獲取鎖定,利用ReleaseMutex() 解除鎖定。建構函式使用如下:

            bool isNew = false;
            mutex = new Mutex(false, "Mutex1", out isNew);

引數1:鎖建立後是否由主調執行緒擁有。 如果設為true,相當於呼叫了WaitOne(),需要釋放,否則其他執行緒無法獲取鎖;

引數2:鎖名稱,可通過OpenExist()或TryOpenExist() 開啟已有鎖,因為作業系統識別有名稱的互鎖,所以可由不同的程序共享。若鎖名稱為空,就是未命名的互鎖,不能在多個程序之間共享;

引數3: 是否為新建立的互鎖;

下面的例子演示Mutex 在程序之間的使用: class Program {

        private static Mutex mutex = null;  
        static void Main(string[] args)
        {
            bool isNew = false;
            mutex = new Mutex(false, "Mutex1", out isNew);
Console.WriteLine("Main Start...."); mutex.WaitOne();
Console.WriteLine("Aquire Lock and Running...."); Thread.Sleep(10000); mutex.ReleaseMutex();
Console.WriteLine("Release Lock...."); Console.WriteLine("Main end...."); Console.ReadLine(); } }

連續2次執行這個控制檯程式的exe,結果如下,首先執行的獲取 Mutex1 互鎖, 後面執行的會等待直到前面執行的釋放 Mutex1 互鎖。

3.Semaphore:訊號量的作用於互斥鎖類似,但它可以定義一定數量的執行緒同時使用。下面是建構函式:

            bool isNew = false;
            semaphore = new Semaphore(3, 3, "semaphore1", out isNew);

引數1:建立後,最初釋放的鎖的數量,如引數1設為2,引數2設為3,則建立後只有2個鎖可用,另1個已經鎖定;

引數2:定義可用鎖的數量;

引數3: 訊號量的名稱,與Mutex類似;

引數4:否為新建立的互鎖;

以下例子建立了訊號量“semaphore1”,利用Parallel.For() 同步執行Func1() ,在Func1() 中,當執行緒獲取訊號量鎖,釋放鎖或等待超時,都會在控制檯裡輸出,

class Program
    {
        private static Semaphore semaphore = null;
        static void Main(string[] args)
        {

            Console.WriteLine("Main Start....");
            bool isNew = false;
            semaphore = new Semaphore(3, 3, "semaphore1", out isNew);
            Parallel.For(0, 6, Func1);
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }

        static void Func1(int index)
        {
            Console.WriteLine("Task {0} Start....",Task.CurrentId);
            bool isComplete = false;
            while (!isComplete)
            {
                if (semaphore.WaitOne(1000))    
                {
                    try
                    {
                        Console.WriteLine("Task {0} aquire lock....", Task.CurrentId);
                        Thread.Sleep(5000);
                    }
                    finally
                    {
                        semaphore.Release();
                        Console.WriteLine("Task {0} release lock....", Task.CurrentId);
                        isComplete = true;
                    }
                }
                else
                {
                    Console.WriteLine("Task {0} timeout....", Task.CurrentId);
                }
            }
        }

執行結果如下,執行緒1,2,3首先獲取訊號量鎖,執行緒4,5,6在等待,直到1,2,3釋放,

4. AutoResetEvent 類:

可以使用事件通知其他任務,建構函式為 public AutoResetEvent(bool initialState)。

當initialState=true,處於signaled 模式(終止狀態),呼叫waitone() 也不會阻塞任務,等待訊號,呼叫Reset()方法,可以設定為non-signaled 模式;

當initialState=fasle,處於non-signaled 模式(非終止狀態),呼叫waitone() 會等待訊號阻塞當前執行緒(可以在多個執行緒中呼叫,同時阻塞多個執行緒),直到呼叫set()傳送訊號釋放執行緒(呼叫一次,只能釋放一個執行緒),一般使用這種方式;

以下例子建立5個任務,分別呼叫waitone()阻塞執行緒,接著每隔2s 呼叫set(),

        private static AutoResetEvent autoReset = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            for (int i = 0; i < 5; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("{0} Start....", Task.CurrentId);
                    autoReset.WaitOne();
                    Console.WriteLine("{0} Continue....", Task.CurrentId);
                });
            }
            for (int i = 0; i < 5;i++ )
            {
                Thread.Sleep(2000);
                autoReset.Set();
            }
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }

執行結果每次順序略有不同,釋放是隨機的:

5.ManualResetEvent 類:功能基本上和AutoSetEvent類似,但又一個不同點:

使用AutoSetEvent,每次呼叫set(),切換到終止模式,只能釋放一個waitone(),便會自動切換到非終止模式;但ManualResetEvent,呼叫set(),切換到終止模式,可以釋放當前所有的waitone(),需要手動呼叫reset()才能切換到非終止模式。

以下例子說明了這個不同的:

        private static ManualResetEvent manualReset = new ManualResetEvent(false);
        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            for (int i = 0; i < 5; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("{0} Start....", Task.CurrentId);
                    manualReset.WaitOne();
                    Console.WriteLine("{0} Continue....", Task.CurrentId);
                });
            }
            Thread.Sleep(2000);
            manualReset.Set();
            manualReset.WaitOne();
            Console.WriteLine("it doesn't work now, Main continue....");
            manualReset.Reset();
            manualReset.WaitOne();
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }