我一直在尋找的.NET TPL的「數據流」庫的某些部分出於好奇的執行情況和我遇到下面的代碼片段來了:線程安全
private void GetHeadTailPositions(out Segment head, out Segment tail,
out int headLow, out int tailHigh)
{
head = _head;
tail = _tail;
headLow = head.Low;
tailHigh = tail.High;
SpinWait spin = new SpinWait();
//we loop until the observed values are stable and sensible.
//This ensures that any update order by other methods can be tolerated.
while (
//if head and tail changed, retry
head != _head || tail != _tail
//if low and high pointers, retry
|| headLow != head.Low || tailHigh != tail.High
//if head jumps ahead of tail because of concurrent grow and dequeue, retry
|| head._index > tail._index)
{
spin.SpinOnce();
head = _head;
tail = _tail;
headLow = head.Low;
tailHigh = tail.High;
}
}
從我對線程安全的理解中,這個操作很容易發生數據競爭。我將解釋我的理解,然後我認爲是'錯誤'。當然,我認爲這在我的心理模型中比在圖書館中更可能是一個錯誤,我希望這裏有人能指出我出錯的地方。
...
所有給定的字段(head
,tail
,head.Low
和tail.High
)是揮發性。在我的理解這給出了兩個保證:
- 每次所有四個字段被讀取,就必須讀取順序
- 編譯器可能沒有的Elid任何的讀取和CLR/JIT必須採取措施爲了防止這些值
的「高速緩存」從我讀給定方法中,發生以下情況:
- 的
ConcurrentQueue
的內部狀態的初始讀取被執行(THA t是head
,tail
,head.Low
和tail.High
)。 - 執行單個忙等待旋
- 然後,該方法再次,並檢查讀出的內部狀態的任何變化
- 如果狀態已改變,則轉到步驟2,重複
- 返回讀取狀態一旦它被認爲是'穩定的'
現在假設全部正確,我的「問題」是這樣的:上述狀態的讀取不是原子的。我沒有看到阻止半寫入狀態的讀取(例如,寫入器線程已更新head
但尚未tail
)。
現在我有點意識到,像這樣的緩衝區中的半寫狀態不是世界的盡頭 - 在所有的head
和tail
指針完全可以獨立更新/讀取之後,通常在CAS /自旋循環。
但是,我真的不知道什麼時候旋轉一下然後再讀一遍。你真的要在一次旋轉的時間裏「捕捉」正在進行的改變嗎?它試圖「防範」什麼?換句話說:如果整個狀態讀取的目的是爲原子的,我不認爲該方法做了什麼幫助,如果沒有,那麼究竟是什麼是該方法在做什麼?
那麼最終呢,這個方法的「雙重檢查」基本上沒有意義麼?或者我誤解了? – Xenoprimate
可能需要的唯一檢查就是'_index',它可以避免在'Segment._next'鏈中返回不指向'_tail'的'_head'。我說*可能*,因爲在'_head'的易失性讀取之後,不應該可以觀察到具有'_tail' **的易失性讀取,因爲在不改變Segment._next '鏈,並在最後加上嚴格增加的指數。其他檢查有一定程度的穩定性(請參閱while語句前的註釋)。 – acelent