我發現this article,但它看起來不對,因爲Cell
不保證在鎖下的set()
和鎖上的get()
之間的同步。在Rust中寫入雙重檢查鎖定的正確方法是什麼?
Atomic_.store(true, Ordering::Release)
是否影響其他非原子寫入操作?
我試圖用AtomicPtr
寫它看起來接近Java風格,但它失敗了。在這種情況下,我找不到正確使用AtomicPtr
的示例。
我發現this article,但它看起來不對,因爲Cell
不保證在鎖下的set()
和鎖上的get()
之間的同步。在Rust中寫入雙重檢查鎖定的正確方法是什麼?
Atomic_.store(true, Ordering::Release)
是否影響其他非原子寫入操作?
我試圖用AtomicPtr
寫它看起來接近Java風格,但它失敗了。在這種情況下,我找不到正確使用AtomicPtr
的示例。
Atomic_.store(true, Ordering::Release)
是否影響其他非原子寫操作?
是。
其實,主要原因Ordering
存在是強加給非原子讀一些排序保證和寫道:
寬鬆
越少約束Ordering
;不能被重新排序的操作只有在相同的原子值操作:
atomic.set(4, Ordering::Relaxed);
other = 8;
println!("{}", atomic.get(Ordering::Relaxed));
保證打印4
。如果另一個線程讀取atomic
爲4
,則不能保證other
是否爲8
。
推出/獲取
寫入和讀出的障礙,分別爲:
store
操作中使用,並保證事先寫入被執行時,load
操作一起使用,並保證進一步的讀取將會看到至少一個值如同在相應的store
之前寫的那樣。所以:
// thread 1
one = 1;
atomic.set(true, Ordering::Release);
two = 2;
// thread 2
while !atomic.get(Ordering::Acquire) {}
println!("{} {}", one, two);
保證one
是1
,並且一無所知two
說。
。注意,Relaxed
商店與Acquire
負荷或Release
存儲與Relaxed
負載本質上是無意義的。
注意防鏽提供AcqRel
:它表現爲Release
商店和Acquire
的負荷,所以你不必記住哪個是哪個?我不建議這樣做雖然,因爲提供的擔保是如此不同。
SeqCst
最受限制的Ordering
。保證一次跨所有線程進行排序。
什麼是寫雙重檢查鎖定在魯斯特的正確方法?
因此,雙重檢查鎖定是利用這些原子操作來避免不必要的鎖定。
的想法是有3個:
,並以此作爲這樣的:
難點在於確保非原子讀取/寫入正確排序(並以正確的順序變爲可見)。從理論上講,你需要完整的圍欄;在實踐中,遵循C11/C++ 11內存模型的習語將是足夠的,因爲編譯器必須使其工作。
讓我們來看看代碼第一(簡體):
struct Lazy<T> {
initialized: AtomicBool,
lock: Mutex<()>,
value: UnsafeCell<Option<T>>,
}
impl<T> Lazy<T> {
pub fn get_or_create<'a, F>(&'a self, f: F) -> &'a T
where
F: FnOnce() -> T
{
if !self.initialized.load(Ordering::Acquire) { // (1)
let _lock = self.lock.lock().unwrap();
if !self.initialized.load(Ordering::Relaxed) { // (2)
let value = unsafe { &mut *self.value.get() };
*value = Some(f(value));
self.initialized.store(true, Ordering::Release); // (3)
}
}
unsafe { &*self.value.get() }.as_ref().unwrap()
}
}
有3個原子操作,通過註釋編號。我們現在可以檢查哪種內存排序保證每個都必須提供正確性。
(1)如果爲true,則返回值的引用,該引用必須引用有效內存。這要求在原子轉換爲真之前執行對該存儲器的寫入操作,並且只有在該真值爲真後才能執行該存儲器的讀取操作。因此(1)要求Acquire
和(3)要求Release
。 (2)另一方面沒有這樣的約束,因爲鎖定一個Mutex
相當於一個完全的內存障礙:所有的寫入都保證在之前發生,所有的讀取只會在之後發生。因此,此負載不需要進一步的保證,所以Relaxed
是最優化的。
因此,就我而言,這種雙重檢查的實現在實踐中看起來是正確的。
對於進一步閱讀,我真的推薦the article by Preshing這是鏈接在你鏈接的一塊。它顯着地突出了理論(圍牆)和實踐(被降低到圍牆的原子裝載/商店)之間的差異。
非常感謝您。但我看到一個錯誤(可能是我錯了)。最後一次讀取* self.value.get()在self.initialized.load(Ordering :: Acquire)== true的情況下查看寫入操作。出於你提到的原因。但是,如果初始化== false,則在已發佈的商店和非原子讀取* self.value.get()之間沒有獲得的負載。例如,在Java中,這是導致值變化的原因(SeqCst)。 –
@АлександрМеньшиков:我不認爲這是一個錯誤。請注意,只有當'self.initialized'爲true時,纔會加載'Acquire'語義:首先加載並知道它是true還是false。因此,即使它最終評估爲「false」,也可以使用「Acquire」語義來執行它,這些語義會在評估它之後對讀取進行排序(並在存儲之前用'Release'語義進行排序)。 –
是的,但是在具有'Release'語義的商店之後,我們不會在同一個線程**中使用'Acquire' **加載,並且只是在比賽中讀取返回的值。並且這個讀取沒有看到寫入,因爲在任何寫入之前調用了「Acquire」語義加載。可能是Rust對序列的保證,所有的讀寫操作都在共享內存的同一線程中 - 那麼你是對的。只是Java不。這是我懷疑的原因。 –
你的問題是一個合理的問題,但它聽起來像[X-Y問題](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)。您可能希望調查像['Once'](https://doc.rust-lang.org/std/sync/struct.Once.html)和[lazy-static](https://crates.io/)這樣的組件板條箱/懶惰靜態)演示[這裏](https://stackoverflow.com/q/27791532/155423)和[這裏](https://stackoverflow.com/q/27221504/155423)。 – Shepmaster