C# 線程同步的多種方式


實際應用中多個線程往往需要共享數據,因此必須使用同步技術,確保一次只有一個線程訪問和改變共享數據。同步又分為進程內部線程的同步以及進程之間線程的同步。

進程內部線程同步:

1. lock : 使用比較簡單 lock(obj){ Synchronize part  };  只能傳遞對象,無法設置等待超時;

2. InterLocked:  原子操作,提供了以線程安全的方式遞增,遞減,交換和讀取值的方法;

3. Monitor: lock語句等同於Monitor.Enter() ,同樣只能傳遞對象,無法設置等待超時,如下:

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

另外使用Monitor.TryEnter(),可以傳遞等待超時,若獲取鎖,則布爾參考變量設為true,執行同步操作;若超時未獲取鎖,則布爾參考變量設為false,執行其他操作; 如下:

            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: 一個抽象基類,用於等待一個信號的設置。 常用方法如下:

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

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

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

Mutex類(Mutual Exclusion 互斥),EventWaitHandle類,Semaphore類 均派生自WaitHandle類。

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釋放,

Main Start....
Task 1 Start....
Task 1 aquire lock....
Task 2 Start....
Task 2 aquire lock....
Task 3 Start....
Task 3 aquire lock....
Task 4 Start....
Task 5 Start....
Task 6 Start....
Task 4 timeout....
Task 5 timeout....
Task 6 timeout....
Task 5 timeout....
Task 4 timeout....
Task 6 timeout....
Task 4 timeout....
Task 5 timeout....
Task 6 timeout....
Task 4 timeout....
Task 5 timeout....
Task 6 timeout....
Task 5 aquire lock....
Task 1 release lock....
Task 4 aquire lock....
Task 6 aquire lock....
Task 2 release lock....
Task 3 release lock....
Task 5 release lock....
Task 4 release lock....
Task 6 release lock....
Main end....

 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();
        }

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

Main Start....
1 Start....
2 Start....
3 Start....
4 Start....
5 Start....
3 Continue....
1 Continue....
4 Continue....
2 Continue....
Main end....
5 Continue....

 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();
        }

運行結果:

Main Start....
1 Start....
2 Start....
3 Start....
4 Start....
5 Start....
5 Continue....
4 Continue....
3 Continue....
2 Continue....
it doesn't work now, Main continue....
1 Continue....


免責聲明!

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



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