注:本文爲我的學習摘錄,原文地址:http://www.cnblogs.com/yank/p/3227324.htmlhtml
在應用程序中使用多個線程的一個好處是每一個線程均可以異步執行。對於 Windows 應用程序,耗時的任務能夠在後臺執行,而使應用程序窗口和控件保持響應。對於服務器應用程序,多線程處理提供了用不一樣線程處理每一個傳入請求的能力。不然,在徹底知足前一個請求以前,將沒法處理每一個新請求。然而,線程的異步特性意味着必須協調對資源(如文件句柄、網絡鏈接和內存)的訪問。不然,兩個或更多的線程可能在同一時間訪問相同的資源,而每一個線程都不知道其餘線程的操做。node
"若是以爲有用,請幫頂! 若是有不足之處,歡迎拍磚!"算法
線程同步有:臨界區、互斥區、事件、信號量四種方式
臨界區(Critical Section)、互斥量(Mutex)、信號量(Semaphore)、事件(Event)的區別
一、臨界區:經過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。在任意時刻只容許一個線程對共享資源進行訪問,若是有多個線程試圖訪問公共資源,那麼在有一個線程進入後,其餘試圖訪問公共資源的線程將被掛起,並一直等到進入臨界區的線程離開,臨界區在被釋放後,其餘線程才能夠搶佔。
二、互斥量:採用互斥對象機制。 只有擁有互斥對象的線程纔有訪問公共資源的權限,由於互斥對象只有一個,因此能保證公共資源不會同時被多個線程訪問。互斥不只能實現同一應用程序的公共資源安全共享,還能實現不一樣應用程序的公共資源安全共享
三、信號量:它容許多個線程在同一時刻訪問同一資源,可是須要限制在同一時刻訪問此資源的最大線程數目
四、事 件: 經過通知操做的方式來保持線程的同步,還能夠方便實現對多個線程的優先級比較的操做緩存
咱們介紹幾種經常使用的C#進行線程同步的方式,這些方式能夠根據其原理,找到對應上面的四種類型之一。安全
一、Interlocked
爲多個線程共享的變量提供原子操做。服務器
根據經驗,那些須要在多線程狀況下被保護的資源一般是整型值,且這些整型值在多線程下最多見的操做就是遞增、遞減或相加操做。Interlocked類提供了一個專門的機制用於完成這些特定的操做。這個類提供了Increment、Decrement、Add靜態方法用於對int或long型變量的遞增、遞減或相加操做。此類的方法能夠防止可能在下列狀況發生的錯誤:計劃程序在某個線程正在更新可由其餘線程訪問的變量時切換上下文;或者當兩個線程在不一樣的處理器上併發執行時。 此類的成員不引起異常。網絡
Increment 和 Decrement 方法遞增或遞減變量並將結果值存儲在單個操做中。 在大多數計算機上,增長變量操做不是一個原子操做,須要執行下列步驟:多線程
1)將實例變量中的值加載到寄存器中。
2)增長或減小該值。
3)在實例變量中存儲該值。
若是不使用 Increment 和 Decrement,線程會在執行完前兩個步驟後被搶先。 而後由另外一個線程執行全部三個步驟。 當第一個線程從新開始執行時,它覆蓋實例變量中的值,形成第二個線程執行增減操做的結果丟失。 併發
Exchange 方法自動交換指定變量的值。 CompareExchange 方法組合了兩個操做:比較兩個值以及根據比較的結果將第三個值存儲在其中一個變量中。 比較和交換操做按原子操做執行。dom
案例分析:共享打印機。
一般咱們會使用共享打印機,幾臺計算機共享一臺打印機,每臺計算機能夠發出打印指令,可能會出現併發狀況。固然咱們知道,打印機採用了隊列技術。爲了簡化操做,咱們假定,在打印機收到命令時,便可打印,並且在同一時間只能有一個打印任務在執行。咱們使用Interlocked方法來實現多線程同步。具體代碼以下:
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { class PrinterWithInterlockTest { /// <summary> /// 正在使用的打印機 /// 0表明未使用,1表明正在使用 /// </summary> public static int UsingPrinter = 0; /// <summary> /// 計算機數量 /// </summary> public static readonly int ComputerCount = 3; /// <summary> /// 測試 /// </summary> public static void TestPrint() { Thread thread; Random random = new Random(); for (int i = 0; i < ComputerCount; i++) { thread = new Thread(MyThreadProc); thread.Name = string.Format("Thread{0}",i); Thread.Sleep(random.Next(3)); thread.Start(); } } /// <summary> /// 線程執行操做 /// </summary> private static void MyThreadProc() { //使用打印機進行打印 UsePrinter(); //當前線程等待1秒 Thread.Sleep(1000); } /// <summary> /// 使用打印機進行打印 /// </summary> private static bool UsePrinter() { //檢查大引進是否在使用,若是原始值爲0,則爲未使用,能夠進行打印,不然不能打印,繼續等待 if (0 == Interlocked.Exchange(ref UsingPrinter, 1)) { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //Code to access a resource that is not thread safe would go here. //Simulate some work Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); //釋放打印機 Interlocked.Exchange(ref UsingPrinter, 0); return true; } else { Console.WriteLine(" {0} was denied the lock", Thread.CurrentThread.Name); return false; } } } }
二、lock 關鍵字
lock 關鍵字將語句塊標記爲臨界區,方法是獲取給定對象的互斥鎖,執行語句,而後釋放該鎖。
lock 確保當一個線程位於代碼的臨界區時,另外一個線程不進入臨界區。若是其餘線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。
public void Function() { System.Object locker= new System.Object(); lock(locker) { // Access thread-sensitive resources. } }
lock 調用塊開始位置的 Enter 和塊結束位置的 Exit。
提供給 lock 關鍵字的參數必須爲基於引用類型的對象,該對象用來定義鎖的範圍。在上例中,鎖的範圍限定爲此函數,由於函數外不存在任何對該對象的引用。嚴格地說,提供給 lock 的對象只是用來惟一地標識由多個線程共享的資源,因此它能夠是任意類實例。然而,實際上,此對象一般表示須要進行線程同步的資源。例如,若是一個容器對象將被多個線程使用,則能夠將該容器傳遞給 lock,而 lock 後面的同步代碼塊將訪問該容器。只要其餘線程在訪問該容器前先鎖定該容器,則對該對象的訪問將是安全同步的。一般,最好避免鎖定 public 類型或鎖定不受應用程序控制的對象實例,例如,若是該實例能夠被公開訪問,則 lock(this) 可能會有問題,由於不受控制的代碼也可能會鎖定該對象。這可能致使死鎖,即兩個或更多個線程等待釋放同一對象。出於一樣的緣由,鎖定公共數據類型(相比於對象)也可能致使問題。鎖定字符串尤爲危險,由於字符串被公共語言運行庫 (CLR)「暫留」。這意味着整個程序中任何給定字符串都只有一個實例,就是這同一個對象表示了全部運行的應用程序域的全部線程中的該文本。所以,只要在應用程序進程中的任何位置處具備相同內容的字符串上放置了鎖,就將鎖定應用程序中該字符串的全部實例。所以,最好鎖定不會被暫留的私有或受保護成員。某些類提供專門用於鎖定的成員。例如,Array 類型提供 SyncRoot。許多集合類型也提供 SyncRoot。
常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此準則:
1)若是實例能夠被公共訪問,將出現 lock (this) 問題。
2)若是 MyType 能夠被公共訪問,將出現 lock (typeof (MyType)) 問題。
3)因爲進程中使用同一字符串的任何其餘代碼將共享同一個鎖,因此出現 lock(「myLock」) 問題。
最佳作法是定義 private 對象來鎖定, 或 private static 對象變量來保護全部實例所共有的數據。關於鎖的研究,你們能夠參考:
http://www.cnblogs.com/yank/archive/2008/10/28/1321119.html
案例分析:繼續使用共享打印機的案例
咱們只需對前面的例子稍做修改便可實現lock進行同步。
聲明鎖對象:
/// <summary> /// 正在使用的打印機 /// </summary> private static object UsingPrinterLocker = new object();
將打印方法修改以下:
/// <summary> /// 使用打印機進行打印 /// </summary> private static void UsePrinter() { //臨界區 lock (UsingPrinterLocker) { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //模擬打印操做 Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); } }
三、監視器
與 lock 關鍵字相似,監視器防止多個線程同時執行代碼塊。Enter 方法容許一個且僅一個線程繼續執行後面的語句;其餘全部線程都將被阻止,直到執行語句的線程調用 Exit。這與使用 lock 關鍵字同樣。事實上,lock 關鍵字就是用 Monitor 類來實現的。例如:(繼續修改共享打印機案例,增長方法UsePrinterWithMonitor)
/// <summary> /// 使用打印機進行打印 /// </summary> private static void UsePrinterWithMonitor() { System.Threading.Monitor.Enter(UsingPrinterLocker); try { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //模擬打印操做 Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); } finally { System.Threading.Monitor.Exit(UsingPrinterLocker); } }
使用 lock 關鍵字一般比直接使用 Monitor 類更可取,一方面是由於 lock 更簡潔,另外一方面是由於 lock 確保了即便受保護的代碼引起異常,也能夠釋放基礎監視器。這是經過 finally 關鍵字來實現的,不管是否引起異常它都執行關聯的代碼塊。
四、同步事件和等待句柄
使用鎖或監視器對於防止同時執行區分線程的代碼塊頗有用,可是這些構造不容許一個線程向另外一個線程傳達事件。這須要「同步事件」,它是有兩個狀態(終止和非終止)的對象,能夠用來激活和掛起線程。讓線程等待非終止的同步事件能夠將線程掛起,將事件狀態更改成終止能夠將線程激活。若是線程試圖等待已經終止的事件,則線程將繼續執行,而不會延遲。
同步事件有兩種:AutoResetEvent 和 ManualResetEvent。它們之間惟一的不一樣在於,不管什麼時候,只要 AutoResetEvent 激活線程,它的狀態將自動從終止變爲非終止。相反,ManualResetEvent 容許它的終止狀態激活任意多個線程,只有當它的 Reset 方法被調用時才還原到非終止狀態。
等待句柄,能夠經過調用一種等待方法,如 WaitOne、WaitAny 或 WaitAll,讓線程等待事件。System.Threading.WaitHandle.WaitOne 使線程一直等待,直到單個事件變爲終止狀態;System.Threading.WaitHandle.WaitAny 阻止線程,直到一個或多個指示的事件變爲終止狀態;System.Threading.WaitHandle.WaitAll 阻止線程,直到全部指示的事件都變爲終止狀態。當調用事件的 Set 方法時,事件將變爲終止狀態。
AutoResetEvent 容許線程經過發信號互相通訊。 一般,當線程須要獨佔訪問資源時使用該類。線程經過調用 AutoResetEvent 上的 WaitOne 來等待信號。 若是 AutoResetEvent 爲非終止狀態,則線程會被阻止,並等待當前控制資源的線程經過調用 Set 來通知資源可用。調用 Set 向 AutoResetEvent 發信號以釋放等待線程。 AutoResetEvent 將保持終止狀態,直到一個正在等待的線程被釋放,而後自動返回非終止狀態。 若是沒有任何線程在等待,則狀態將無限期地保持爲終止狀態。若是當 AutoResetEvent 爲終止狀態時線程調用 WaitOne,則線程不會被阻止。 AutoResetEvent 將當即釋放線程並返回到非終止狀態。
能夠經過將一個布爾值傳遞給構造函數來控制 AutoResetEvent 的初始狀態:若是初始狀態爲終止狀態,則爲 true;不然爲 false。
AutoResetEvent 也能夠同 staticWaitAll 和 WaitAny 方法一塊兒使用。
案例:
案例介紹:
今天咱們來作飯,作飯呢,須要一菜、一粥。今天咱們吃魚。
熬粥和作魚,是比較複雜的工做流程,
作粥:選材、淘米、熬製
作魚:洗魚、切魚、醃製、烹調
爲了提升效率,咱們用兩個線程來準備這頓飯,可是,如今只有一口鍋,只能等一個作完以後,另外一個才能進行最後的烹調。
來看實例代碼:
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { /// <summary> /// 案例:作飯 /// 今天的Dinner準備吃魚,還要熬粥 /// 熬粥和作魚,是比較複雜的工做流程, /// 作粥:選材、淘米、熬製 /// 作魚:洗魚、切魚、醃製、烹調 /// 咱們用兩個線程來準備這頓飯 /// 可是,如今只有一口鍋,只能等一個作完以後,另外一個才能進行最後的烹調 /// </summary> class CookResetEvent { /// <summary> /// /// </summary> private AutoResetEvent resetEvent = new AutoResetEvent(false); /// <summary> /// 作飯 /// </summary> public void Cook() { Thread porridgeThread = new Thread(new ThreadStart(Porridge)); porridgeThread.Name = "Porridge"; porridgeThread.Start(); Thread makeFishThread = new Thread(new ThreadStart(MakeFish)); makeFishThread.Name = "MakeFish"; makeFishThread.Start(); //等待5秒 Thread.Sleep(5000); resetEvent.Reset(); } /// <summary> /// 熬粥 /// </summary> public void Porridge() { //選材 Console.WriteLine("Thread:{0},開始選材", Thread.CurrentThread.Name); //淘米 Console.WriteLine("Thread:{0},開始淘米", Thread.CurrentThread.Name); //熬製 Console.WriteLine("Thread:{0},開始熬製,須要2秒鐘", Thread.CurrentThread.Name); //須要2秒鐘 Thread.Sleep(2000); Console.WriteLine("Thread:{0},粥已經作好,鍋閒了", Thread.CurrentThread.Name); resetEvent.Set(); } /// <summary> /// 作魚 /// </summary> public void MakeFish() { //洗魚 Console.WriteLine("Thread:{0},開始洗魚",Thread.CurrentThread.Name); //醃製 Console.WriteLine("Thread:{0},開始醃製", Thread.CurrentThread.Name); //等待鍋空閒出來 resetEvent.WaitOne(); //烹調 Console.WriteLine("Thread:{0},終於有鍋了", Thread.CurrentThread.Name); Console.WriteLine("Thread:{0},開始作魚,須要5秒鐘", Thread.CurrentThread.Name); Thread.Sleep(5000); Console.WriteLine("Thread:{0},魚作好了,好香", Thread.CurrentThread.Name); resetEvent.Set(); } } }
ManualResetEvent與AutoResetEvent用法基本相似,這裏很少作介紹。
五、Mutex對象
mutex 與監視器相似;它防止多個線程在某一時間同時執行某個代碼塊。事實上,名稱「mutex」是術語「互相排斥 (mutually exclusive)」的簡寫形式。然而與監視器不一樣的是,mutex 能夠用來使跨進程的線程同步。mutex 由 Mutex 類表示。當用於進程間同步時,mutex 稱爲「命名 mutex」,由於它將用於另外一個應用程序,所以它不能經過全局變量或靜態變量共享。必須給它指定一個名稱,才能使兩個應用程序訪問同一個 mutex 對象。
儘管 mutex 能夠用於進程內的線程同步,可是使用 Monitor 一般更爲可取,由於監視器是專門爲 .NET Framework 而設計的,於是它能夠更好地利用資源。相比之下,Mutex 類是 Win32 構造的包裝。儘管 mutex 比監視器更爲強大,可是相對於 Monitor 類,它所須要的互操做轉換更消耗計算資源。
本地 mutex 和系統 mutex
Mutex 分兩種類型:本地 mutex 和命名系統 mutex。 若是使用接受名稱的構造函數建立了 Mutex 對象,那麼該對象將與具備該名稱的操做系統對象相關聯。 命名的系統 mutex 在整個操做系統中均可見,而且可用於同步進程活動。 您能夠建立多個 Mutex 對象來表示同一命名系統 mutex,並且您可使用 OpenExisting 方法打開現有的命名系統 mutex。
本地 mutex 僅存在於進程當中。 進程中引用本地 Mutex 對象的任意線程均可以使用本地 mutex。 每一個 Mutex 對象都是一個單獨的本地 mutex。
在本地Mutex中,用法與Monitor基本一致
繼續修改前面的打印機案例:
聲明Mutex對象:
/// <summary> /// mutex對象 /// </summary> private static Mutex mutex = new Mutex();
具體操做:
/// <summary> /// 使用打印機進行打印 /// </summary> private static void UsePrinterWithMutex() { mutex.WaitOne(); try { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //模擬打印操做 Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); } finally { mutex.ReleaseMutex(); } }
多線程調用:
/// <summary> /// 測試 /// </summary> public static void TestPrint() { Thread thread; Random random = new Random(); for (int i = 0; i < ComputerCount; i++) { thread = new Thread(MyThreadProc); thread.Name = string.Format("Thread{0}", i); Thread.Sleep(random.Next(3)); thread.Start(); } } /// <summary> /// 線程執行操做 /// </summary> private static void MyThreadProc() { //使用打印機進行打印 //UsePrinter(); //monitor同步 //UsePrinterWithMonitor(); //用Mutex同步 UsePrinterWithMutex(); //當前線程等待1秒 Thread.Sleep(1000); }
最後的打印機案例代碼:
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { class PrinterWithLockTest { /// <summary> /// 正在使用的打印機 /// </summary> private static object UsingPrinterLocker = new object(); /// <summary> /// 計算機數量 /// </summary> public static readonly int ComputerCount = 3; /// <summary> /// mutex對象 /// </summary> private static Mutex mutex = new Mutex(); /// <summary> /// 測試 /// </summary> public static void TestPrint() { Thread thread; Random random = new Random(); for (int i = 0; i < ComputerCount; i++) { thread = new Thread(MyThreadProc); thread.Name = string.Format("Thread{0}", i); Thread.Sleep(random.Next(3)); thread.Start(); } } /// <summary> /// 線程執行操做 /// </summary> private static void MyThreadProc() { //使用打印機進行打印 //UsePrinter(); //monitor同步 //UsePrinterWithMonitor(); //用Mutex同步 UsePrinterWithMutex(); //當前線程等待1秒 Thread.Sleep(1000); } /// <summary> /// 使用打印機進行打印 /// </summary> private static void UsePrinter() { //臨界區 lock (UsingPrinterLocker) { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //模擬打印操做 Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); } } /// <summary> /// 使用打印機進行打印 /// </summary> private static void UsePrinterWithMonitor() { System.Threading.Monitor.Enter(UsingPrinterLocker); try { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //模擬打印操做 Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); } finally { System.Threading.Monitor.Exit(UsingPrinterLocker); } } /// <summary> /// 使用打印機進行打印 /// </summary> private static void UsePrinterWithMutex() { mutex.WaitOne(); try { Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name); //模擬打印操做 Thread.Sleep(500); Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name); } finally { mutex.ReleaseMutex(); } } } }
六、讀取器/編寫器鎖
ReaderWriterLockSlim 類容許多個線程同時讀取一個資源,但在向該資源寫入時要求線程等待以得到獨佔鎖。
能夠在應用程序中使用 ReaderWriterLockSlim,以便在訪問一個共享資源的線程之間提供協調同步。 得到的鎖是針對 ReaderWriterLockSlim 自己的。
設計您應用程序的結構,讓讀取和寫入操做的時間儘量最短。 由於寫入鎖是排他的,因此長時間的寫入操做會直接影響吞吐量。 長時間的讀取操做會阻止處於等待狀態的編寫器,而且,若是至少有一個線程在等待寫入訪問,則請求讀取訪問的線程也將被阻止。
案例:構造一個線程安全的緩存
using System; using System.Threading; using System.Collections.Generic; namespace MutiThreadSample.ThreadSynchronization { /// <summary> /// 同步Cache /// </summary> public class SynchronizedCache { private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private Dictionary<int, string> innerCache = new Dictionary<int, string>(); /// <summary> /// 讀取 /// </summary> /// <param name="key"></param> /// <returns></returns> public string Read(int key) { cacheLock.EnterReadLock(); try { return innerCache[key]; } finally { cacheLock.ExitReadLock(); } } /// <summary> /// 添加項 /// </summary> /// <param name="key"></param> /// <param name="value"></param> public void Add(int key, string value) { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } } /// <summary> /// 添加項,有超時限制 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <param name="timeout"></param> /// <returns></returns> public bool AddWithTimeout(int key, string value, int timeout) { if (cacheLock.TryEnterWriteLock(timeout)) { try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return true; } else { return false; } } /// <summary> /// 添加或者更新 /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <returns></returns> public AddOrUpdateStatus AddOrUpdate(int key, string value) { cacheLock.EnterUpgradeableReadLock(); try { string result = null; if (innerCache.TryGetValue(key, out result)) { if (result == value) { return AddOrUpdateStatus.Unchanged; } else { cacheLock.EnterWriteLock(); try { innerCache[key] = value; } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Updated; } } else { cacheLock.EnterWriteLock(); try { innerCache.Add(key, value); } finally { cacheLock.ExitWriteLock(); } return AddOrUpdateStatus.Added; } } finally { cacheLock.ExitUpgradeableReadLock(); } } /// <summary> /// 刪除項 /// </summary> /// <param name="key"></param> public void Delete(int key) { cacheLock.EnterWriteLock(); try { innerCache.Remove(key); } finally { cacheLock.ExitWriteLock(); } } /// <summary> /// /// </summary> public enum AddOrUpdateStatus { Added, Updated, Unchanged }; } }
七、Semaphore 和 SemaphoreSlim
System.Threading.Semaphore 類表示一個命名(系統範圍)信號量或本地信號量。 它是一個對 Win32 信號量對象的精簡包裝。 Win32 信號量是計數信號量,可用於控制對資源池的訪問。
SemaphoreSlim 類表示一個輕量的快速信號量,可用於在一個預計等待時間會很是短的進程內進行等待。 SemaphoreSlim 會盡量多地依賴由公共語言運行時 (CLR) 提供的同步基元。 可是,它也會根據須要提供延遲初始化的、基於內核的等待句柄,以支持等待多個信號量。 SemaphoreSlim 還支持使用取消標記,但它不支持命名信號量或使用等待句柄來進行同步。
線程經過調用 WaitOne 方法來進入信號量,此方法是從 WaitHandle 類派生的。 當調用返回時,信號量的計數將減小。 當一個線程請求項而計數爲零時,該線程會被阻止。 當線程經過調用 Release 方法釋放信號量時,將容許被阻止的線程進入。 並不保證被阻塞的線程進入信號量的順序,例如先進先出 (FIFO) 或後進先出 (LIFO)。信號量的計數在每次線程進入信號量時減少,在線程釋放信號量時增長。 當計數爲零時,後面的請求將被阻塞,直到有其餘線程釋放信號量。 當全部的線程都已釋放信號量時,計數達到建立信號量時所指定的最大值。
案例分析:購買火車票
還得排隊進行購買,購買窗口是有限的,只有窗口空閒時才能購買
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { /// <summary> /// 案例:支付流程 /// 如超市、藥店、火車票等,都有限定的幾個窗口進行結算,只有有窗口空閒,才能進行結算。 /// 咱們就用多線程來模擬結算過程 /// </summary> class PaymentWithSemaphore { /// <summary> /// 聲明收銀員總數爲3個,可是當前空閒的個數爲0,可能還沒開始上班。 /// </summary> private static Semaphore IdleCashiers = new Semaphore(0, 3); /// <summary> /// 測試支付過程 /// </summary> public static void TestPay() { ParameterizedThreadStart start = new ParameterizedThreadStart(Pay); //假設同時有5我的來買票 for (int i = 0; i < 5; i++) { Thread thread = new Thread(start); thread.Start(i); } //主線程等待,讓全部的的線程都激活 Thread.Sleep(1000); //釋放信號量,2個收銀員開始上班了或者有兩個空閒出來了 IdleCashiers.Release(2); } /// <summary> /// /// </summary> /// <param name="obj"></param> public static void Pay(object obj) { Console.WriteLine("Thread {0} begins and waits for the semaphore.", obj); IdleCashiers.WaitOne(); Console.WriteLine("Thread {0} starts to Pay.",obj); //結算 Thread.Sleep(2000); Console.WriteLine("Thread {0}: The payment has been finished.",obj); Console.WriteLine("Thread {0}: Release the semaphore.", obj); IdleCashiers.Release(); } } }
八、障礙(Barrier)4.0後技術
使多個任務可以採用並行方式依據某種算法在多個階段中協同工做。
經過在一系列階段間移動來協做完成一組任務,此時該組中的每一個任務發信號指出它已經到達指定階段的 Barrier 而且暗中等待其餘任務到達。 相同的 Barrier 可用於多個階段。
九、SpinLock(4.0後)
SpinLock結構是一個低級別的互斥同步基元,它在等待獲取鎖時進行旋轉。 在多核計算機上,當等待時間預計較短且極少出現爭用狀況時,SpinLock 的性能將高於其餘類型的鎖。 不過,咱們建議您僅在經過分析肯定 System.Threading.Monitor 方法或 Interlocked 方法顯著下降了程序的性能時使用 SpinLock。
即便 SpinLock 未獲取鎖,它也會產生線程的時間片。 它這樣作是爲了不線程優先級別反轉,並使垃圾回收器可以繼續執行。 在使用 SpinLock 時,請確保任何線程持有鎖的時間不會超過一個很是短的時間段,並確保任何線程在持有鎖時不會阻塞。
因爲 SpinLock 是一個值類型,所以,若是您但願兩個副本都引用同一個鎖,則必須經過引用顯式傳遞該鎖。
using System; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MutiThreadSample.ThreadSynchronization { class SpinLockSample { public static void Test() { SpinLock sLock = new SpinLock(); StringBuilder sb = new StringBuilder(); Action action = () => { bool gotLock = false; for (int i = 0; i < 100; i++) { gotLock = false; try { sLock.Enter(ref gotLock); sb.Append(i.ToString()); } finally { //真正獲取以後,才釋放 if (gotLock) sLock.Exit(); } } }; //多線程調用action Parallel.Invoke(action, action, action); Console.WriteLine("輸出:{0}",sb.ToString()); } } }
十、SpinWait(4.0後)
System.Threading.SpinWait 是一個輕量同步類型,能夠在低級別方案中使用它來避免內核事件所需的高開銷的上下文切換和內核轉換。 在多核計算機上,當預計資源不會保留很長一段時間時,若是讓等待線程以用戶模式旋轉數十或數百個週期,而後從新嘗試獲取資源,則效率會更高。 若是在旋轉後資源變爲可用的,則能夠節省數千個週期。 若是資源仍然不可用,則只花費了少許週期,而且仍然能夠進行基於內核的等待。 這一旋轉-等待的組合有時稱爲「兩階段等待操做」。
下面的基本示例採用微軟案例:無鎖堆棧
using System; using System.Threading; namespace MutiThreadSample.ThreadSynchronization { public class LockFreeStack<T> { private volatile Node m_head; private class Node { public Node Next; public T Value; } public void Push(T item) { var spin = new SpinWait(); Node node = new Node { Value = item }, head; while (true) { head = m_head; node.Next = head; if (Interlocked.CompareExchange(ref m_head, node, head) == head) break; spin.SpinOnce(); } } public bool TryPop(out T result) { result = default(T); var spin = new SpinWait(); Node head; while (true) { head = m_head; if (head == null) return false; if (Interlocked.CompareExchange(ref m_head, head.Next, head) == head) { result = head.Value; return true; } spin.SpinOnce(); } } } }
總結:
儘管有這麼多的技術,可是不一樣的技術對應不一樣的場景,咱們必須熟悉其特色和適用範圍。在應用時,必須具體問題具體分析,選擇最佳的同步方式。