overview
同步基元分為用戶模式和內核模式
用戶模式:Iterlocked.Exchange(互鎖)、SpinLocked(自旋鎖)、易變構造(volatile關鍵字、
volatile
類、Thread.VolatitleRead|Thread.VolatitleWrite
)、MemoryBarrier。
.net中的System.Threading命名空間的Interlocked類可以為多個線程共享的變量提供原子操作。
經驗顯示,那些需要在多線程下被保護的資源通常是整型的,而這些被共享的整型值最常見的操作就是增加、減少。Interlocked類提供了一個專門的機制用於完成這些特定的操作。
Iterlocked.Exchange()
不只是原子的,它還具有內存可見性。
所有 方法都生成一個完整的柵欄。因此,您通過訪問的字段不需要額外的柵欄。
Interlocked 輕量級鎖
Interlocked.Increment(ref value) 數值加一(原子性操作)
Interlocked.Decrement(ref value) 數值減一(原子性操作)
Interlocked.Exchange(ref value1, value2) 交換:把值2賦給值1;返回新值
Interlocked.CompareExchange(ref value1, value2, value3) 實現比較和交換兩種功能:值1和值3比較,如果相同,把值2給值1,不相同則不作任何操作;返回原值(多用於判斷條件)(示例3中會用到)
Interlocked.MemoryBarrier :按如下方式同步內存存取:執行當前線程的處理器在對指令重新排序時,不能采用先執行 MemoryBarrier() 調用之后的內存存取,再執行 MemoryBarrier() 調用之前的內存存取的方式。Thread.MemoryBarrier 就是包裝了它。
個人補充:值刷新當前執行線程的cpu上的store buffer和Invalidate queue都運行完成了.
Interlocked.MemoryBarrierProcessWide:內部執行FlushProcessWriteBuffers函數()。該函數功能刷新正在運行當前進程所有線程的每個處理器的寫入隊列(store buffer)。該函數為屬於當前進程關聯一部分的所有處理器生成一個處理器間中斷 (IPI)。它保證了在一個處理器上對另一個處理器執行的寫入操作的可見性。
個人補充:將當前進程下所有線程所在的cpu上的store buffer和Invalidate queue都運行完成了,所以非常耗費時間 。
注意不要傳入 volatile類型。
Interlocked.Add 方法:以原子操作的形式,添加兩個整數並用兩者的和替換第一個整數。
Add(Int32, Int32)|Add(Int64, Int64)Add|(UInt32, UInt32)|Add(UInt64, UInt64)
對兩個 32 位整數進行求和並用和替換第一個整數,上述操作作為一個原子操作完成。
And(Int32, Int32)方法:對兩個 32 位帶符號整數進行按位“與”運算,並用結果替換第一個整數,上述操作作為一個原子操作完成。
Add(Int32, Int32)|Add(Int64, Int64)Add|(UInt32, UInt32)|Add(UInt64, UInt64)
對兩個 32 位帶符號整數進行按位“與”運算,並用結果替換第一個整數,上述操作作為一個原子操作完成。
Interlocked.Or 方法:對兩個 32 位帶符號整數進行按位“或”運算,並用結果替換第一個整數,上述操作作為一個原子操作完成。
Add(Int32, Int32)|Add(Int64, Int64)Add|(UInt32, UInt32)|Add(UInt64, UInt64)
對兩個 32 位帶符號整數進行按位“或”運算,並用結果替換第一個整數,上述操作作為一個原子操作完成。
Interlocked.Read 方法 :返回一個以原子操作形式加載的 64 位值。
Read(Int64) |Read(UInt64)
返回一個以原子操作形式加載的 64 位值。
MemoryBarrierProcessWide方法與“普通”MemoryBarrier方法的區別如下:
正常的memory barrier可以確保當前CPU的讀寫不能跨越barrier。進程級內存屏障確保了進程中使用的任何CPU的任何讀或寫操作都不能跨越這個屏障。
如果每個訪問數據的線程都使用barrier,那么正常的內存barrier允許合理的共享訪問。進程級內存屏障迫使其他cpu與進程內存同步(例如,刷新寫緩沖區和同步讀緩沖區)。這允許在某些線程上進行非連鎖操作,並且仍然有合理的共享訪問。
正常的內存屏障的開銷很小;正常的連鎖操作可能花費不到100個周期。進程級內存屏障非常昂貴。它必須迫使進程中的每個CPU做一些事情,可能要花費數千個周期。
MemoryBarrierProcessWide方法還受到無鎖編程的所有微妙之處的影響。然而,當您實際需要調用它時,這個方法可能非常有用,這種情況應該很少見。
該方法封裝了對Windows上的FlushProcessWriteBuffers和Linux上的sys_membarrier的調用。
案例來自:
這個案例主要考驗對 volatile關鍵字和內存屏障的理解。
該案例會出乎意料的輸出0,0 。為了避免該情況必須使用全內存屏障。
該例子來自:volatile的內存屏障的坑
using System; using System.Threading; using System.Threading.Tasks; namespace MemoryBarriers { class Program { static volatile int x, y, a, b; static void Main() { while (true) { var t1 = Task.Run(Test1); var t2 = Task.Run(Test2); Task.WaitAll(t1, t2); if (a == 0 && b == 0) { Console.WriteLine("{0}, {1}", a, b); } x = y = a = b = 0; } } static void Test1() { x = 1; //方案一 Interlocked.MemoryBarrier(); //方案二 Interlocked.MemoryBarrierProcessWide(); a = y; } static void Test2() { y = 1; //方案一 Interlocked.MemoryBarrier(); b = x; } } }