![](/img/trans.png)
[英]fetch_sub with memory_order_relaxed for atomic reference counting?
[英]How can memory_order_relaxed work for incrementing atomic reference counts in smart pointers?
考慮以下摘自 Herb Sutter 關於原子的演講的代碼片段:
smart_ptr 類包含一個名為 control_block_ptr 的 pimpl 對象,其中包含引用計數refs 。
// Thread A:
// smart_ptr copy ctor
smart_ptr(const smart_ptr& other) {
...
control_block_ptr = other->control_block_ptr;
control_block_ptr->refs.fetch_add(1, memory_order_relaxed);
...
}
// Thread D:
// smart_ptr destructor
~smart_ptr() {
if (control_block_ptr->refs.fetch_sub(1, memory_order_acq_rel) == 1) {
delete control_block_ptr;
}
}
Herb Sutter 說線程 A 中refs的增量可以使用 memory_order_relaxed 因為“沒有人根據動作做任何事情”。 現在,據我所知,memory_order_relaxed,如果refs在某個時刻等於 N 並且兩個線程 A 和 B 執行以下代碼:
control_block_ptr->refs.fetch_add(1, memory_order_relaxed);
那么可能會發生兩個線程都看到refs的值是 N 並且都將 N+1 寫回它。 這顯然不起作用,並且應該像使用析構函數一樣使用 memory_order_acq_rel。 我哪里錯了?
EDIT1:考慮以下代碼。
atomic_int refs = N; // at time t0.
// [Thread 1]
refs.fetch_add(1, memory_order_relaxed); // at time t1.
// [Thread 2]
n = refs.load(memory_order_relaxed); // starting at time t2 > t1
refs.fetch_add(1, memory_order_relaxed);
n = refs.load(memory_order_relaxed);
在調用 fetch_add 之前,線程 2 觀察到的refs值是多少? 它可能是 N 或 N+1? 調用 fetch_add 后線程 2 觀察到的 refs 值是多少? 必須至少是 N+2 嗎?
[討論 URL:C++ & Beyond 2012 - http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2 (@ 1: 20:00)]
模擬std::atomic
Boost.Atomic 庫提供了類似的引用計數示例和解釋,它可能有助於您的理解。
始終可以使用
memory_order_relaxed
來增加引用計數器:對對象的新引用只能從現有引用形成,並且將現有引用從一個線程傳遞到另一個線程必須已經提供了任何所需的同步。在另一個線程中刪除對象之前,強制執行對一個線程中對象的任何可能訪問(通過現有引用),這一點很重要。 這是通過刪除引用后的“釋放”操作(通過此引用對對象的任何訪問顯然必須發生在之前)和刪除對象之前的“獲取”操作來實現的。
可以將
memory_order_acq_rel
用於 fetch_sub 操作,但是當引用計數器尚未達到零時,這會導致不需要的“獲取”操作,並且可能會造成性能損失。
來自std::memory_order
上的 C++ 參考:
memory_order_relaxed:放寬操作:沒有對其他讀或寫施加同步或排序約束,僅保證該操作的原子性
在該頁面下方還有一個示例。
所以基本上, std::atomic::fetch_add()
仍然是原子的,即使使用std::memory_order_relaxed
,因此來自 2 個不同線程的並發refs.fetch_add(1, std::memory_order_relaxed)
總是將refs
增加 2。重點內存順序是其他非原子或std::memory_order_relaxed
原子操作如何在指定內存順序的當前原子操作周圍重新排序。
由於這相當令人困惑(至少對我而言),因此我將部分解決一個問題:
(...)那么可能會發生兩個線程都看到 refs 的值為 N 並且都將 N+1 寫回它(...)
根據@AnthonyWilliams 在這個答案中的說法,上面的句子似乎是錯誤的:
保證您擁有“最新”值的唯一方法是使用讀取-修改-寫入操作,例如 exchange()、compare_exchange_strong() 或 fetch_add()。 讀-修改-寫操作有一個額外的約束,它們總是對“最新”值進行操作,因此一系列線程的 ai.fetch_add(1) 操作序列將返回一個沒有重復或間隙的值序列。 在沒有額外約束的情況下,仍然無法保證哪些線程會看到哪些值。
因此,考慮到權限參數,我想說兩個線程都不可能看到從N到N+1 的值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.