C#內置提供的原子操作
- Interlocked.Increment:以原子操作的形式遞增指定變量的值並存儲結果。
- Interlocked.Decrement:以原子操作的形式遞減指定變量的值並存儲結果。
- Interlocked.Add:以原子操作的形式,添加兩個整數並用兩者的和替換第一個整數
問題:如果要進行原子的乘法、除法或者其他操作改怎么辦,C#並沒有內置提供相應的方法呀?
那我們先來大概理解一下原子操作的流程
以增加變量值為例
- 將實例變量添加到CPU寄存器中
- 將該變量的值進行增加
- 將該變量的增加后的值從CPU寄存器中還原到堆或棧中實例變量的值
可以看到如果是多核CPU在多線程環境下可能會導致在執行完步驟1,步驟2后當前線程失去時間片,其他線程讀取改變量的值時就會讀取到並未增加增加之前的值,所以就導致了數據的不一致。然而C#提供了上面三種原子操作來保證不出現這樣的數據不一致,至於底層原理暫時不深究,我們來看看如何實現除這三種方法之外的操作。
進行原子的獲取最大值操作
先放代碼如下:
public static int Maximum(ref int target, int value)
{
//注意target前面加了 ref ,這樣在方法外改變target的值將會影響到方法內的target值,
//即類似按引用傳遞
int currentVal = target;//使用currentVal局部變量來存儲target值,target值的變更不會影響currentVal
int startVal = 0;
int desiredVal = 0;
do
{
startVal = currentVal; //記錄本次迭代的起始值 1
desiredVal = Math.Max(startVal, value);//根據startVal和value計算最大值desiredVal 2
//如果target和startVal值一致則用desiredVal賦值給target,並將target原始值進行返回
//如果target和startVal值不相同則什么都不做,並將target的值進行返回
//target的值因為在別的線程進行操作時可能改變target的值,所以導致target值和startVal不一致
currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);// 3
} while (startVal != currentVal); // 4
//因為startVal記錄的是開始時target的值,而currentVal記錄的則是target最新值
//如果startVale和currentVal不一致則代表其他線程已經更改的target的值,所以需要重新迭代。
//重新迭代時currentVal代表的是target的最新值
//疑問:如果在 3 之后 4之前 target的值被其他線程更改怎么辦?請大神幫忙
return desiredVal;
}
大致的邏輯可以參考注釋進行理解,最主要的方法是通過currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal); 可以參考MSDN此方法的解釋
其他操作
C# VIA CLR中給出了其他操作的模板,可以參考如下:
delegate int Morpher<TResult, TArgument>(int startValue, TArgument argument, out TResult morphResult);
static TResult Morph<TResult,TArgument>(ref int target,TArgument argument,Morpher<TResult,TArgument> morpher)
{
TResult morphResult;
int currentVal = target;
int startVal = 0;
int desiredVal = 0;
do
{
startVal = currentVal;
desiredVal = morpher(startVal, argument, out morphResult);
currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);
} while (startVal != currentVal);
return morphResult;
}
可以通過委托定義自己想要的操作。乘法、除法等等。
大神寫出的書就是牛,膜拜Jeffrey Richter