2016-01-20 99 views
1

我有兩個線程,生產者和消費者。數據交換由兩個指針的std ::原子公司內部控制:如何強制std :: atomic在其他std :: atomic被寫入後被讀取?

std::atomic<TNode*> next = nullptr; 
std::atomic<TNode*> waiting = nullptr; 

螺紋生產者出臺所準備的數據和事後檢查等待的值:

TNode* newNext = new TNode(); 
// ... fill *newNext ... 
next.store(newNext, std::memory_order_release); 
TNode* oldWaiting = waiting.load(std::memory_order_seq_cst); 
if(oldWaiting == nullptr) 
{ 
    /* wake up Consumer */ 
} 

它是crucical負載在waiting之後來到next,但std::memory_order_seq_cst的店鋪比我真正需要更強的保證,因爲我真的只需要這兩個訪問固定的順序。 是否有可能在不需要memory_order_seq_cst的情況下獲得我需要的內存順序?

下面是圖片的其餘部分:

螺紋消費者檢查next。如果它發現它爲空,它會設置waiting在阻塞自身之前發信號給Producer。

TNode* newCurrent = next.load(std::memory_order_consume); 
if(newCurrent == nullptr) 
{ 
    waiting.store(current, std::memory_order_relaxed); 
    /* wait, blocking, for next != nullptr */ 
} 
current = newCurrent; 

整個事情是一個生產者 - 消費者隊列,在不需要所有複雜機制的情況下保持低鎖定的需要。 next實際上是一個單鏈表的當前節點。數據通常以突發形式出現,因此在大多數情況下,消費者會發現大量節點已準備好用於消費;除了極少數情況外,兩個線程都只在突發之間經過一次鎖定和阻塞/喚醒。

回答

-2

簡短的回答:

編譯器可以自由地重新排序存儲器存取(或者即使在不揮發的Elid他們)不同之處在於:

如果指定商店是memory_order_release負載指定memory_order_acquire之前,那麼編譯器必須尊重你的意圖,而不是重新排序負載以「在商店之前發生」。

順序一致性將實現這一點,而不會給維護人員頭疼。它在即將到來的arm 8上也是最優效率的,它將是第一個正確實現指令負載採集和存儲釋放的處理器。

你可以找到你所需要的一切瞭解一下這兩個會談C++原子能公司:

https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2

我會建議他們必須觀看試圖與原子能任何事情之前。

看完它們之後,你可能會意識到,即使你認爲你做過,你以前也不知道有關原子的事情。這對我來說當然是這種情況。

1

你實質上是在尋找memory_order_release的鏡像順序。那是memory_order_acquire

這比你要求的稍強。在.load之後沒有內存訪問可以重新排序。但是,CPU通常不提供部分排序兩次訪問的方法,因此C++也沒有這種粒度。

C++理論上也有release/consume排序,但沒有人真的需要它。

+0

據我所知'memory_order_acquire'只限制其他讀取在加載之前被重新排序,但不會限制寫入,也不會阻止它在加載後移動讀取。或者我錯了? –

+0

延遲加載最容易理解。編譯器總是可以延遲其他負載(假設它們本身沒有被排序)。如果另一個線程正在向負載被延遲的變量寫入新值,這種延遲只能給出不同的結果,這意味着無論如何你都有競爭條件。記憶順序在存在這種未定義行爲時沒有意義。我不認爲隨機寫入會受到傷害,因爲寫入被另一個線程拾取,用於計算,然後用於通過獲取語義來影響負載的計算。然後你有一個依賴。 – MSalters

2

要非常小心。你將需要一個釋放圍欄和一個獲取圍欄的地方,以確保您執行的寫入期間:

TNode* newNext = new TNode(); 
// ... fill *newNext ... 

消費者可見。

最近您可以做的是在消費者然後執行原子的「放鬆」閱讀,然後執行獲取並開始「消費」該對象。 在一些(大多數?)架構上可能沒有任何作用。

請閱讀'使用獲取和釋放柵欄的演練'http://preshing.com/20130922/acquire-and-release-fences/

我無法寫出更接近你正在做的工作示例的東西。製片人/消費者是(面對它)教科書的挑戰。

稍有問題。我會用std::condition_variable。他們是爲此而製作的。

稍微偏離問題我不太喜歡你的鎖定策略。 這取決於生產者/消費者可能需要多長時間,但是如果像你這樣的生產者'爆發'說它可能是一個壞主意,阻止它白色消費者正在工作。你已經有效地讓他們輪流。 你可以做什麼(只需要一點小心),讓Producer能夠在隊列後面的(TNodes)上推動工作,幾乎不受消費者的阻礙。所以如果消費者需要一段時間,生產者可能不會構成延遲開銷。

這是使不具有一個設計:

/* wait, blocking, for next != nullptr */ 

這阻礙了

TNode* newNext = new TNode(); 
// ... fill *newNext ... 

在接下來的工作項目。 NB:如果消費者在邏輯上必須完成纔可能發生,那麼此任務的並行性的整個想法就會變得糟糕,而且您可能會順序執行。

+0

對不起,我感到困惑。 'next'實際上在'TNode'內。生產者和消費者都跟蹤他們正在工作的節點並獨立前進,因此製作人可以保持追加節點而不會阻塞。只有消費者會阻止,並且只有在沒有節點準備就緒的情況下(也就是說,「下一個」尚未由消費者當前節點中的生產者設置)。我實際上使用'std :: condition_variable'作爲阻塞部分;但由於這是一個高度優化的實現,我避免將它用於每個節點。是的,我很小心。如果我沒有學習如何多年,我不會嘗試這個。 –

相關問題