【C# 線程】 atomic action原子操作|primitive(基元、原語)


概念

原子操作(atomic action):也叫primitive(原語、基元),它是操作系統用語范疇。指由若干條指令組成的,用於完成一定功能的一個過程。  原語是由若干個機器指令構成的完成某種特定功能的一段程序,具有不可分割性·即原語的執行必須是連續的,在執行過程中不允許被中斷。

操作系統只需在執行以下操作時暫時屏蔽全部中斷:測試信號量、更新信號量以及在需要時使某個進程睡眠。由於這些動作只需要幾條指令,所以屏蔽中斷不會帶來什么副作用。如果使用多個CPU,則每個信號量應由一個鎖變量進行保護。

在.net中實現原子操作的類是 Interlocked類。CAS在.NET中的實現類是Interlocked

Interlocked類主要方法

interlocked是基於CAS操作

CAS操作基於CPU提供的原子操作指令實現。只能保證共享變量操作的原子:

對於Intel X86處理器,可通過在匯編指令前增加LOCK前綴來鎖定系統總線,使系統總線在匯編指令執行時無法訪問相應的內存地址。而各個編譯器根據這個特點實現了各自的原子操作函數。

 CAS是一種有名的無鎖算法。無鎖編程(指C#代碼中不加鎖,匯編代碼會自動加鎖),即不適用鎖的情況下實現多線程之間的變量同步,也就是在沒有現成被阻塞的情況下實現變量的同步。

CompareExchange(ref a ,b,c):比較吧a,c是否相等,如果相等,則用b替換a的值。
CompareExchange<T>(T, T, T)  比較兩個指定的引用類型的實例 T 是否相等,如果相等,則替換第一個。好多原子操作都是基於這個函數實現的。
Decrement(): 安全遞減1,相當於 i--
Exchange(): 安全交換數據,相當於 a = 30
Increment() :安全遞加1,相當於 i++
Add() :安全相加一個數值,相當於 a = a + 3
Read() : 安全讀取數值,相等於int a=b

案例:用5個線程從0數到1億

 

using System.Diagnostics; class Program { static long counter = 1; /// <summary> /// 開5個線程 從0數到1億 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Parallel.Invoke(f1, f1, f1, f1, f1); // f1();  Console.WriteLine(stopwatch.ElapsedMilliseconds); Console.WriteLine(counter); } static void f1() { for (int i = 1; i <= 10_000_000; i++) { Interlocked.Increment(ref counter); // counter++;  } } } //5個線程花費時間 1608ms //單線程 125 ms 

本以為5個線程會更快,結果還不如一個線程快。這是什么問題???

因為Interlocked.Increment是采用CAS操作

CAS是一種有名的無鎖算法。無鎖編程(指編程語言方面),即不適用鎖的情況下實現多線程之間的變量同步,也就是在沒有現成被阻塞的情況下實現變量的同步。

CAS原理

CAS,是“Compare And Swap”的縮寫,中文簡稱就是“比較並替換”。

在這個機制中有三個核心的參數:

  • 主內存中存放的共享變量的值:V(一般情況下這個V是內存的地址值,通過這個地址可以獲得內存中的值)
  • 工作內存中共享變量的副本值,也叫預期值:A
  • 需要將共享變量更新到的最新值:B

 

如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內存A中,然后計算后變成B值,最后再把B值寫回到內存V值中。多個線程共用V值都是如此操作。CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經被其他線程改變,重新將V值賦給A(自旋),並重新計算得到B,如果相同,則將B值賦給V。

值得注意的是CAS機制中的這步步驟是原子性的(從cpu指令層面提供的原子操作),所以CAS機制可以解決多線程並發編程對共享變量讀寫的原子性問題。

CAS的適用場景

讀多寫少:如果有大量的寫操作,CPU開銷可能會過大,因為沖突失敗后會不斷重試(自旋),這個過程中會消耗CPU

cas操作多出比較和寫入內存,所以要耗費太多時間了。而單線程不用Interlocked 直接用緩存的數據進行累加,所以單線程更快。


免責聲明!

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



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