2012-01-11 176 views
30

在C++ 11中,有大量新的隨機數生成器引擎和分佈函數。他們線程安全嗎?如果您在多個線程中共享一個隨機分佈和引擎,是否安全,您仍然會收到隨機數字?我期待的場景是一樣的東西,C++ 11隨機數生成器的線程安全性

void foo() { 
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
#pragma omp parallel for 
    for (int i = 0; i < 1000; i++) { 
     double a = zeroToOne(engine); 
    } 
} 

使用OpenMP或

void foo() { 
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
    dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) { 
     double a = zeroToOne(engine); 
    }); 
} 

使用libdispatch。

回答

25

的C++ 11標準庫被廣泛線程安全的。 PRNG對象上的線程安全保證與容器上的相同。更具體地說,因爲PRNG類都是 -random,即它們根據當前確定的狀態生成確定性序列,所以在包含狀態之外的任何地方都沒有空間窺探或戳動(這也是可見的用戶)。

正如容器需要鎖定以使它們安全地共享一樣,您將不得不鎖定PRNG對象。這會使其變得緩慢而不確定。每個線程一個對象會更好。

§17.6.5.9[res.on.data.races]:

1本節指定實現中應滿足 防止數據爭要求(1.10)。除非另有規定,否則每個標準庫函數 均符合各項要求。在下面指定的情況下,實現可能會阻止數據競爭。

2 A C++標準庫函數應不直接或間接 訪問對象(1.10)由比當前 線程除非對象經由 函數的論元發言:包括此直接或間接訪問的其它線程訪問。

3 A C++標準庫函數應不直接或間接地 修改對象(1.10)由比當前 線程除非對象經由 功能的非 - const參數,包括本直接或間接訪問的其它線程訪問。

4 [注:這意味着,例如,實現不能用於內部一個 靜態對象不同步,因爲它 可能導致數據的比賽即使在沒有明確共享 對象betweenthreads程序。 -endnote]

5 A C++標準庫函數不應訪問的對象通過其參數或經由其容器 參數的元件間接 訪問除了通過調用這些容器元件由它的規範 所需的功能。

6上的操作通過調用一個標準庫 容器或字符串成員函數可以訪問底層 容器獲得的迭代器,但不應修改它。 [注意:特別是,迭代器無效的容器 操作與 與該容器關聯的迭代器的操作發生衝突。 - 結束註釋]

7如果對象對用戶不可見並且受到數據保護,則實現可以在線程之間共享它們自己的內部對象 比賽。

8除非另有規定,否則C++標準庫函數將 僅在當前線程內執行所有操作,如果這些操作對用戶具有可見的效果(1.10)。

9 [注意:如果 沒有可見的副作用,這允許實現並行操作。 - 結束注意]

+0

這基本上是我認爲它不是線程安全的。是否可以共享分配對象'std :: uniform_real_distribution zeroToOne(0.0,1.0)'線程數量並且每個線程使用一個引擎? – user1139069 2012-01-13 17:24:48

+0

@ user1139069:不,不安全。儘管乍看之下,分發對象*可以通過簡單地將每個調用委託給引擎對象來完成工作,而不需要維護內部狀態,但是如果您考慮一下,不會產生足夠多的隨機位的引擎可能需要調用兩次。但是兩次(或一次)可能會過度,所以允許緩存多餘的隨機比特可能會更好。 §26.5.1.6\t「隨機數字分配要求」允許這樣做;分配對象特別具有隨每次調用而改變的狀態。因此,他們應該被視爲鎖定目的引擎的一部分。 – Potatoswatter 2012-01-14 02:44:36

0

documentation隻字不提線程安全的,所以我會認爲他們是線程安全的。

+12

cppreference.com上沒有提到它並不是如此。 – Potatoswatter 2012-01-11 08:03:11

2

標準(井N3242)似乎沒有提到隨機數生成是免費的(除了rand不是),所以它不是(除非我錯過了某些東西)。此外,讓它們線程化並沒有意義,因爲它會產生相對較高的開銷(至少與數字本身的產生相比),而沒有真正贏得任何東西。此外,我並沒有真正看到有一個共享隨機數生成器的好處,而不是每個線程都有一個,每個線程都有不同的初始化(例如,從另一個生成器的結果或當前線程ID)。畢竟,你可能不會依賴發電機產生一定的順序,反正每次運行。所以,我會重寫你的代碼,這樣的事情(爲openmp,沒有任何線索約libdispatch):

void foo() { 
    #pragma omp parallel 
    { 
    //just an example, not sure if that is a good way too seed the generation 
    //but the principle should be clear 
    std::mt19937_64 engine((omp_get_thread_num() + 1) * static_cast<uint64_t>(system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
    #pragma omp for 
     for (int i = 0; i < 1000; i++) { 
      double a = zeroToOne(engine); 
     } 
    } 
} 
+1

實際上,如果從不同的線程讀取相同的RNG,即使對於固定的種子,您也不能依賴獲得相同的一系列隨機數,因爲調度可能會導致在不同線程上從不同線程訪問RNG的順序不同。所以*特別是*如果您需要可重現的隨機數字序列,則不應在線程之間共享RNG。 – celtschk 2012-01-11 08:19:54

+0

@celtschk:這取決於如何定義獲得相同的序列。我會說一個人會得到相同的序列(globaly),只是線程會在每次運行時看到它的不同部分。 – Grizzly 2012-01-11 13:37:18