2017-10-12 397 views
2

我正在使用C++ 11,並且我知道由於std::vector對bools的專門化,併發寫入到std::vector<bool> someArray不是線程安全的。C++併發寫入數組(而不是std :: vector)bools

我試圖找出是否寫入bool someArray[2048]有同樣的問題:

  • 假設在someArray所有條目初始設置爲
  • 假設我有一堆線程在someArray.的不同索引處寫入實際上,這些線程只將假的的不同數組條目設置爲true。
  • 假設我有一個閱讀器線程,在某個時間點獲取一個鎖,觸發內存圍欄操作。

問:將讀者看到所有的寫操作someArray被收購的鎖之前發生的?

謝謝!

+0

如果您需要線程安全性,請使用['std :: atomic_bool'](http://en.cppreference.com/w/cpp/atomic/atomic)並將其稱爲完成 – Mgetz

回答

3

您應該使用std::array<bool, 2048> someArray而不是bool someArray[2048];。如果您使用的是C++ 11版本,則需要儘可能多地更新代碼。

std::array<bool, N>std::vector<bool>相同,因此在原始安全性方面沒有問題。

至於你的實際問題:

將讀者看到所有的寫操作someArray被收購的鎖之前發生的?

只有當作家到陣列還與鎖交互,無論是在他們寫完的時候釋放它,或者通過更新與鎖讀者然後同步關聯的值。如果編寫者從不與鎖進行交互,那麼讀取器將檢索的數據是未定義的。

有一件事你還需要牢記:雖然它並不不安全有多個線程寫入同一陣列,前提是它們都寫入唯一存儲器地址,寫作可以很慢通過與高速緩存的交互顯着。例如:

void func_a() { 
    std::array<bool, 2048> someArray{}; 
    for(int i = 0; i < 8; i++) { 
     std::thread writer([i, &someArray]{ 
      for(size_t index = i * 256; index < (i+1) * 256; index++) 
       someArray[index] = true; 
      //Some kind of synchronization mechanism you need to work out yourself 
     }); 
     writer.detach(); 
    } 
} 

void func_b() { 
    std::array<bool, 2048> someArray{}; 
    for(int i = 0; i < 8; i++) { 
     std::thread writer([i, &someArray]{ 
      for(size_t index = i; index < 2048; index += 8) 
       someArray[index] = true; 
      //Some kind of synchronization mechanism you need to work out yourself 
     }); 
     writer.detach(); 
    } 
} 

的細節將會根據底層硬件上有所不同,但在幾乎所有情況下,func_a將是數量級比func_b更快,至少對於一個足夠大的陣列大小(2048被選爲一個例子,但它可能不代表實際的潛在性能差異)。這兩個函數應該有相同的結果,但其中一個會比另一個快得多。

+0

謝謝Xirema!這是非常好的知道,我讀過另一個SO帖子中類似的東西。另外,我實際上有一個'std :: atomic ',所有編寫者都會在編寫布爾數組之後遞減。我想讀者可以簡單地閱讀相同的原子變量來觸發內存籬笆? –

+1

@AlinTomescu是的,這可能就足夠了。但是,我建議您改爲編寫/獲取適當的* Reader/Writer Lock *來保證正確的行爲,否則使用Mutex + Semaphore(這是大多數讀寫器鎖實現的方式)。 – Xirema

+0

@Xirema或者他們可以使用原子......不需要讀寫器鎖定。 – Mgetz

0

首先,一般std :: vector不像您想象的那樣是線程安全的。該擔保已經規定here

解決您的問題:讀者可能會在而不是獲取鎖後看到所有寫入。這是由於這樣的事實,即寫入者可能永遠不會執行釋放操作,該操作是在寫入和隨後的讀取之間建立發生關係之前所需的釋放操作。用(非常)簡單的術語來說:每個獲取操作(例如互斥鎖)都需要一個釋放操作來同步。任何釋放到某個可見元素之前完成的內存操作對於獲取相同變量的任何線程都是可見的。另見Release-Acquire ordering

+0

要清楚,匹配鎖定互斥鎖時固有的獲取操作是解鎖操作中固有的釋放操作。*表示相同的互斥鎖*。即這兩個操作通過對互斥體的地址進行rendezvousing來建立排序。釋放/獲取部分也可以在bools數組上使用atomic_store和atomic_load來建立,儘管避免競爭仍然需要某種形式的靜音。 –

+0

謝謝!發佈獲取順序實際上很好知道。但是,似乎'std :: vector '(當'T!= bool')確實支持併發寫入:請參見「線程安全」部分[此處]中的第3項(http://en.cppreference.com/w/cpp/container),它表示_「顯然,如果你使用'push_back',就像前面的SO中所描述的那樣,」同一個容器中的不同元素可以被不同的線程同時修改,除了std :: vector 。帖子,這不是線程安全的。 –

0

要注意的一件重要的事情是int32大小的變量(如bool)上的所有操作(獲取和存儲)都是原子的(x86或x64體系結構都適用)。所以如果你聲明你的數組是volatile(必要的,因爲每個線程可能有一個緩存的數組值),你不應該在修改數組時有任何問題(通過多線程)。

相關問題