簡體   English   中英

字段由多個線程讀取/寫入,Interlocked與volatile

[英]Fields read from/written by several threads, Interlocked vs. volatile

在SO上有很多關於Interlocked vs. volatile的問題,我理解並且知道volatile的概念(沒有重新排序,總是從內存中讀取等)並且我知道Interlocked工作方式是它執行的原子操作。

但我的問題是:假設我有一個從多個線程讀取的字段,這是一些引用類型,例如: public Object MyObject; 我知道,如果我對它做一個比較交換,就像這樣: Interlocked.CompareExchange(ref MyObject, newValue, oldValue) ,互鎖保證只將newValue寫入ref MyObject引用的內存位置,如果ref MyObjectoldValue當前引用對同一個對象。

但閱讀呢? Interlocked是否保證在CompareExchange操作成功后讀取MyObject任何線程將立即獲得新值,或者我是否必須將MyObject標記為volatile來確保這一點?

我想知道的原因是我已經實現了一個無鎖鏈接列表,當你為它添加一個元素時,它會不斷地更新其內部的“head”節點,如下所示:

[System.Diagnostics.DebuggerDisplay("Length={Length}")]
public class LinkedList<T>
{
    LList<T>.Cell head;

    // ....

    public void Prepend(T item)
    {
        LList<T>.Cell oldHead;
        LList<T>.Cell newHead;

        do
        {
            oldHead = head;
            newHead = LList<T>.Cons(item, oldHead);

        } while (!Object.ReferenceEquals(Interlocked.CompareExchange(ref head, newHead, oldHead), oldHead));
    }

    // ....
}

現在Prepend成功之后,線程讀取head保證獲得最新版本,即使它沒有標記為volatile

我一直在做一些實證測試,它似乎工作得很好,我在這里搜索過但沒有找到明確的答案(一堆不同的問題和評論/答案都說是相互矛盾的事情)。

Interlocked是否保證在CompareExchange操作成功后讀取MyObject的任何線程將立即獲得新值,或者我是否必須將MyObject標記為volatile來確保這一點?

是的, 在同一個線程上的后續讀取將獲得新值。

你的循環展開到這個:

oldHead = head;
newHead = ... ;

Interlocked.CompareExchange(ref head, newHead, oldHead) // full fence

oldHead = head; // this read cannot move before the fence

編輯

正常緩存可能發生在其他線程上。 考慮:

var copy = head;

while ( copy == head )
{
}

如果你在另一個線程上運行它,編譯器可以緩存head的值,永遠不會看到更新。

你的代碼應該工作正常。 雖然沒有明確記錄, Interlocked.CompareExchange方法將產生一個全柵欄屏障。 我想你可以做一個小改動,省略Object.ReferenceEquals調用,轉而依賴於!=運算符,默認情況下會執行引用相等。

值得一提的是, InterlockedCompareExchange Win API調用的文檔要好得多。

此函數生成完整的內存屏障(或柵欄),以確保按順序完成內存操作。

遺憾的是,.NET BCL對應的Interlocked.CompareExchange上不存在相同級別的文檔,因為它很可能映射到CAS的完全相同的底層機制。

現在Prepend成功之后,線程讀取頭是否保證獲得最新版本,即使它沒有標記為volatile?

不,不一定。 如果這些線程不生成獲取柵欄屏障,則無法保證它們將讀取最新值。 確保在使用head時執行易失性讀取。 您已使用Interlocked.CompareExchange調用確保在Prepend 當然,該代碼可能會以過時的head值經過循環一次,但由於Interlocked操作,下一次迭代將被刷新。

因此,如果您的問題的上下文與其他正在執行Prepend線程相關,那么就不需要做任何其他事情了。

但是,如果您的問題的上下文是關於在LinkedList上執行另一個方法的其他線程,那么請確保在適當的地方使用Thread.VolatileReadInterlocked.CompareExchange

旁注......可能會對以下代碼執行微優化。

newHead = LList<T>.Cons(item, oldHead);

我看到的唯一問題是內存是在循環的每次迭代中分配的。 在高爭用期間,循環可能在最終成功之前旋轉幾次。 只要在每次迭代時將鏈接引用重新分配給oldHead ,您就可以將此行提升到循環之外(這樣您就可以獲得新的讀取)。 這樣,內存只分配一次。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM