大家都曉得.NET中執行緒同步有以下幾種方式:
臨界區(Critical Section)、互斥量(Mutex)、訊號量(Semaphore)、事件(Event)
1、臨界區:通過對多執行緒的序列化來訪問公共資源或一段程式碼,速度快,適合控制資料訪問。在任意時刻只允許一個執行緒對共享資源進行訪問,如果有多個執行緒試圖訪問公共資源,那麼在有一個執行緒進入後,其他試圖訪問公共資源的執行緒將被掛起,並一直等到進入臨界區的執行緒離開,臨界區在被釋放後,其他執行緒才可以搶佔。
2、互斥量:採用互斥物件機制。 只有擁有互斥物件的執行緒才有訪問公共資源的許可權,因為互斥物件只有一個,所以能保證公共資源不會同時被多個執行緒訪問。互斥不僅能實現同一應用程式的公共資源安全共享,還能實現不同應用程式的公共資源安全共享
3、訊號量:它允許多個執行緒在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大執行緒數目
4、事 件: 通過通知操作的方式來保持執行緒的同步,還可以方便實現對多個執行緒的優先順序比較的操作
臨界區
Interlocked:為多個執行緒共享的變數提供原子操作
此類的方法可以防止可能在下列情況發生的錯誤:計劃程式在某個執行緒正在更新可由其他執行緒訪問的變數時切換上下文;或者當兩個執行緒在不同的處理器上併發執行時。 此類的成員不引發異常。那麼問題來了,為什麼是原子級?為什麼共有訪問許可權的例項變數會線上程上下文切換的時候丟失結果?
在大多數計算機上,增加變數操作不是一個原子操作,需要執行下列步驟:
1)將例項變數中的值載入到暫存器中
2)改變該值
3)在例項變數中儲存該值
所以當執行緒A執行了前兩步之後,執行緒B(可能是單核的cpu上下文切換,也可能是多核中的其他執行緒)對同一個例項變數執行了三部曲,那麼A在暫存器中的操作就會被覆蓋,值就丟失了。如果換成Interlocked.Increment/Decrement,按照我的猜測,它對於值的原子操作是一種不可打斷性的,類似鎖。windows程式設計中也提供有類似的Interlocked關鍵字的函式,同樣具有原子性操作特性,但它的實現方式是基於“鎖定記憶體”以達到隔離其他執行緒訪問該記憶體的目的,詳見《windows核心程式設計》。
Interlocked.Exchange(ref a, 0),交換a和0,並返回a。
Interlocked.CompareExchange(ref isSington, 1, 0),目標運算元(第1引數所指向的記憶體中的數)與一個值(第3引數)比較,如果相等,則用另一個值(第2引數)與目標運算元(第1引數所指向的記憶體中的數)交換,此方法用處比較多,最常見的是單例模式。
public class ConsumerCancel { private int isSington = 0; public bool ConsumeAsync(Action<IMessage> action, int backThreadCount) { try { if (Interlocked.CompareExchange(ref isSington, 1, 0) != 0) return true;//保證單例,已執行的不再執行 } catch (Exception ex) { //xxxx } } }
下面這個是Interlocked類的原始碼
namespace System.Threading { using System; using System.Security.Permissions; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.Versioning; using System.Runtime; // After much discussion, we decided the Interlocked class doesn't need // any HPA's for synchronization or external threading. They hurt C#'s // codegen for the yield keyword, and arguably they didn't protect much. // Instead, they penalized people (and compilers) for writing threadsafe // code. public static class Interlocked { /****************************** * Increment * Implemented: int * long *****************************/ [ResourceExposure(ResourceScope.None)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static int Increment(ref int location) { return Add(ref location, 1); } [ResourceExposure(ResourceScope.None)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static long Increment(ref long location) { return Add(ref location, 1); } /****************************** * Decrement * Implemented: int * long *****************************/ [ResourceExposure(ResourceScope.None)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static int Decrement(ref int location) { return Add(ref location, -1); } [ResourceExposure(ResourceScope.None)] public static long Decrement(ref long location) { return Add(ref location, -1); } /****************************** * Exchange * Implemented: int * long * float * double * Object * IntPtr *****************************/ [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern int Exchange(ref int location1, int value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern long Exchange(ref long location1, long value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern float Exchange(ref float location1, float value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern double Exchange(ref double location1, double value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern Object Exchange(ref Object location1, Object value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern IntPtr Exchange(ref IntPtr location1, IntPtr value); [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Runtime.InteropServices.ComVisible(false)] [System.Security.SecuritySafeCritical] public static T Exchange<T>(ref T location1, T value) where T : class { _Exchange(__makeref(location1), __makeref(value)); //Since value is a local we use trash its data on return // The Exchange replaces the data with new data // so after the return "value" contains the original location1 //See ExchangeGeneric for more details return value; } [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] private static extern void _Exchange(TypedReference location1, TypedReference value); /****************************** * CompareExchange * Implemented: int * long * float * double * Object * IntPtr *****************************/ [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern int CompareExchange(ref int location1, int value, int comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern long CompareExchange(ref long location1, long value, long comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern float CompareExchange(ref float location1, float value, float comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [System.Security.SecuritySafeCritical] public static extern double CompareExchange(ref double location1, double value, double comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern Object CompareExchange(ref Object location1, Object value, Object comparand); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] public static extern IntPtr CompareExchange(ref IntPtr location1, IntPtr value, IntPtr comparand); /***************************************************************** * CompareExchange<T> * * Notice how CompareExchange<T>() uses the __makeref keyword * to create two TypedReferences before calling _CompareExchange(). * This is horribly slow. Ideally we would like CompareExchange<T>() * to simply call CompareExchange(ref Object, Object, Object); * however, this would require casting a "ref T" into a "ref Object", * which is not legal in C#. * * Thus we opted to cheat, and hacked to JIT so that when it reads * the method body for CompareExchange<T>() it gets back the * following IL: * * ldarg.0 * ldarg.1 * ldarg.2 * call System.Threading.Interlocked::CompareExchange(ref Object, Object, Object) * ret * * See getILIntrinsicImplementationForInterlocked() in VM\JitInterface.cpp * for details. *****************************************************************/ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Runtime.InteropServices.ComVisible(false)] [System.Security.SecuritySafeCritical] public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class { // _CompareExchange() passes back the value read from location1 via local named 'value' _CompareExchange(__makeref(location1), __makeref(value), comparand); return value; } [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] private static extern void _CompareExchange(TypedReference location1, TypedReference value, Object comparand); // BCL-internal overload that returns success via a ref bool param, useful for reliable spin locks. [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [System.Security.SecuritySafeCritical] internal static extern int CompareExchange(ref int location1, int value, int comparand, ref bool succeeded); /****************************** * Add * Implemented: int * long *****************************/ [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] internal static extern int ExchangeAdd(ref int location1, int value); [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern long ExchangeAdd(ref long location1, long value); [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static int Add(ref int location1, int value) { return ExchangeAdd(ref location1, value) + value; } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static long Add(ref long location1, long value) { return ExchangeAdd(ref location1, value) + value; } /****************************** * Read *****************************/ public static long Read(ref long location) { return Interlocked.CompareExchange(ref location,0,0); } public static void MemoryBarrier() { Thread.MemoryBarrier(); } } }
訊號量,隨筆中有專門介紹的,不再多說了。
案例:購買火車票
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(); } } } 購買火車票
互斥量,最常見的莫過於lock(object)了,不多說。
public void Function() { System.Object locker= new System.Object(); lock(locker) { // Access thread-sensitive resources. } }
事件,同步事件和等待控制程式碼。這裡引用”停留的風“的描述和用例。
使用鎖或監視器對於防止同時執行區分執行緒的程式碼塊很有用,但是這些構造不允許一個執行緒向另一個執行緒傳達事件。這需要“同步事件”,它是有兩個狀態(終止和非終止)的物件,可以用來啟用和掛起執行緒。讓執行緒等待非終止的同步事件可以將執行緒掛起,將事件狀態更改為終止可以將執行緒啟用。如果執行緒試圖等待已經終止的事件,則執行緒將繼續執行,而不會延遲。同步事件有兩種: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(); } } }
其他方式,引用來自”停留的風“
1.Mutex物件
mutex與監視器類似;它防止多個執行緒在某一時間同時執行某個程式碼塊。事實上,名稱“mutex”是術語“互相排斥 (mutually exclusive)”的簡寫形式。然而與監視器不同的是,mutex可以用來使跨程式的執行緒同步。mutex 由Mutex類表示。當用於程式間同步時,mutex稱為“命名 mutex”,因為它將用於另一個應用程式,因此它不能通過全域性變數或靜態變數共享。必須給它指定一個名稱,才能使兩個應用程式訪問同一個mutex物件。
儘管mutex可以用於程式內的執行緒同步,但是使用Monitor通常更為可取,因為監視器是專門為.NETFramework而設計的,因而它可以更好地利用資源。相比之下,Mutex類是Win32構造的包裝。儘管mutex比監視器更為強大,但是相對於Monitor類,它所需要的互操作轉換更消耗計算資源。
本地mutex和系統mutex
Mutex分兩種型別:本地mutex和命名系統mutex。如果使用接受名稱的建構函式建立了Mutex物件,那麼該物件將與具有該名稱的作業系統物件相關聯。命名的系統mutex在整個作業系統中都可見,並且可用於同步程式活動。您可以建立多個Mutex物件來表示同一命名系統 mutex,而且您可以使用OpenExisting方法開啟現有的命名系統mutex。
本地 mutex 僅存在於程式當中。 程式中引用本地 Mutex 物件的任意執行緒都可以使用本地 mutex。 每個 Mutex 物件都是一個單獨的本地 mutex。
在本地Mutex中,用法與Monitor基本一致。
/// <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); }
2.讀取器/編寫器鎖
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 }; } }
3.障礙(Barrier)4.0後技術
使多個任務能夠採用並行方式依據某種演算法在多個階段中協同工作。通過在一系列階段間移動來協作完成一組任務,此時該組中的每個任務發訊號指出它已經到達指定階段的 Barrier 並且暗中等待其他任務到達。 相同的 Barrier 可用於多個階段。
4.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()); } } }
5.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(); } } } }
以上的介紹中我也只是用過一小部分,有些場景沒有遇到過,可能無法體會甚至理解,收藏起來僅供學習研究。