更有效(較少總線鎖定和更少的讀取)和簡化執行什麼馬克貼:
static int InterlockedIncrementAndClamp(ref int count, int max)
{
int oldval = Volatile.Read(ref count), val = ~oldval;
while(oldval != max && oldval != val)
{
val = oldval;
oldval = Interlocked.CompareExchange(ref count, oldval + 1, oldval);
}
return oldval + 1;
}
如果你有非常高的競爭,我們也許能夠提高可擴展性進一步通過減少常見的情況到一個單一的原子增量指令:與CompareExchange相同的開銷,但沒有循環的機會。
static int InterlockedIncrementAndClamp(ref int count, int max, int drift)
{
int v = Interlocked.Increment(ref count);
while(v > (max + drift))
{
// try to adjust value.
v = Interlocked.CompareExchange(ref count, max, v);
}
return Math.Min(v, max);
}
在這裏,我們允許count
要上去drift
價值超過max
。但我們仍然只能返回max
。這使我們可以在大多數情況下將整個操作數摺疊成單個原子增量,這將允許最大的可伸縮性。如果我們超出我們的drift
價值,那麼我們只需要一個以上的操作,您可以將其設置得足夠稀少。
響應於Marc的約聯鎖和非互鎖存儲器訪問憂慮一起工作:
關於具體volatile
VS互鎖:volatile
只是一個普通存儲器的運算,但一個未優化掉和一個這與其他記憶操作不會重新排序。這個特定的問題並不是圍繞這些特定屬性中的任何一個,所以我們真的在談論非互鎖與互鎖互操作性。
.NET內存模型保證讀取和寫入基本整數類型(直到機器的本地字大小)並且引用是原子的。互鎖方法也是原子的。因爲.NET只有一個「原子」的定義,所以它們不需要明確的特殊情況說明它們相互兼容。
一件事Volatile.Read
不不保證是知名度:您總能獲得一個加載指令,但CPU可能讀取其本地緩存,而不是僅僅把內存由不同CPU的新值的舊值。在大多數情況下,x86並不需要擔心這個問題(像MOVNTPS
這樣的特殊指令是個例外),但對於其他架構來說這是一件非常可能的事情。總之,這裏描述了兩個可能影響Volatile.Read
的問題:首先,我們可以在16位CPU上運行,在這種情況下,讀取int
不會是原子的,我們讀的可能不是原子的價值別人正在寫作。其次,即使它是原子性的,由於可見性,我們可能會讀取舊值。
但是影響Volatile.Read
並不意味着它們會影響整個算法,這對於這些算法是完全安全的。
如果您以同時以非原子方式書寫至count
,第一種情況只會咬我們。這是因爲最終會發生的是(寫入A [0]; CAS A [0:1];寫入A [1])。因爲我們所有的寫入count
發生在保證原子CAS中,所以這不是問題。當我們剛剛閱讀時,如果我們讀了一個錯誤的值,它會在即將到來的CAS中被捕獲。
如果你仔細想想,第二種情況實際上只是一個正常情況下的讀寫特性變化的專業化 - 讀取只是在我們要求它之前發生。在這種情況下,第一個Interlocked.CompareExchange
調用會報告與Volatile.Read
給出的值不同的值,並且您將開始循環直到成功。
如果您願意,您可以將Volatile.Read
視爲對低爭用情況的純優化。我們可以用0
初始化oldval
,它仍然可以正常工作。使用Volatile.Read
給它一個很高的機會,只執行一個CAS(其中,如指令所示,在多CPU配置中相當昂貴)而不是兩個。
但是,正如Marc所說 - 有時鎖只是更簡單!
'〜'是怎麼回事? –
只是爲了確保在第一次通過時使用'val!= oldval'。 –
啊,夠公平的。 –