2012-11-25 15 views

回答

6

更有效(較少總線鎖定和更少的讀取)和簡化執行什麼馬克貼:

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所說 - 有時鎖只是更簡單!

+0

'〜'是怎麼回事? –

+0

只是爲了確保在第一次通過時使用'val!= oldval'。 –

+0

啊,夠公平的。 –

4

沒有一個「比較,如果不相等」,但是:你可以先測試自己的價值,然後只做更新,如果你沒有得到一個線程的比賽;這通常意味着如果第二次測試失敗,您可能需要循環。在僞代碼中:

bool retry; 
do { 
    retry = false; 
    // get current value 
    var val = Interlocked.CompareExchange(ref field, 0, 0); 
    if(val != max) { // if not maxed 
     // increment; if the value isn't what it was above: redo from start 
     retry = Interlocked.CompareExchange(ref field, val + 1, val) != val; 
    }   
} while (retry); 

但坦率地說,鎖會更簡單。

+0

+1 to:「一個鎖會更簡單」 –

+0

什麼是volatile而不是Interlocked.CompareExchange(ref field,0,0)? – user1748906

+0

@ user1748906不知道它說「volatile」將遵循與「互鎖」相同的隔離/芳香性規則。它可能會工作,但是......我想這取決於你期望有多少衝突,就像我已經說過的那樣:坦率地說,在很多情況下鎖會更簡單 –