「Treiber Stack」通常是最簡單的無鎖數據結構之一,因此在教授無鎖算法介紹時經常使用它。C++ Treiber堆棧和原子下一個指針
我見過很多使用C++ atomics的Treiber Stacks的實現。算法本身是微不足道的,所以真正的挑戰是處理無鎖數據結構的所有其他附帶細節,例如提供某種方式來執行安全的內存回收,避免ABA問題,並以無鎖方式分配節點。這可以通過各種方式解決,例如使用原子引用計數,危險指針,計數/標記指針以避免ABA,以及使用無鎖內存池。
但忽略所有這些細節,專注於簡單的算法本身,也發生在我一個問題是,二極管驅動器堆棧的每一個實現,我記得使用實現原子下一個指針節點類的事實。例如:
struct Node
{
T value;
std::atomic<Node*> next;
};
但考慮到算法後,我不知道爲什麼下一個指針需要原子。
一般PUSH算法(忽略無鎖分配,安全內存回收,回退,避免ABA等):
Node* n = new Node();
Node* front = m_front.load();
n->next.store(front);
while (!m_front.compare_exchange_weak(front, n))
{
n->next.store(front);
}
一般的POP算法(再次,無視除了實際的算法所有細節邏輯)是:
Node* front = m_front.load();
Node* next = front->next.load();
while (!m_front.compare_exchange_weak(front, next))
{
next = front->next.load();
}
這裏是一個真實的例子執行PUSH算法:
https://github.com/khizmax/libcds/blob/master/cds/intrusive/treiber_stack.h#L736
所以我不明白爲什麼下一個指針甚至需要是原子的。大多數C++實現使用next
指針來使用寬鬆的加載/存儲,所以在讀/寫下一個指針時我們不需要任何內存限制,但是我的想法是它根本不需要是原子的。
從我所看到的,任何時候都不會寫入任何節點的下一個指針。相反,下一個指針可以同時加載,但我從來沒有看到算法同時加載+存儲或同時存儲+存儲的機會。實際上,在PUSH算法中,根本不會同時訪問下一個指針。
因此,在我看來,下一個指針在同時訪問時是有效的「只讀」,所以我不知道爲什麼甚至有必要使它們成爲原子。然而,我看到的Treiber Stack的每個C++實現都使得下一個指針是原子的。所以我是否正確,或者有什麼理由讓下一個指針必須是原子的?
因爲沒有原子,你沒有CAS? – cHao
你不要在下一個指針 – Siler
上用CAS。我明白你現在在說什麼。在C++中原子性,排序和可見性相互關聯的可能性很小。 Java似乎不需要'next'的原子,但是它的內存模型本身提供了一些同步保證。 – cHao