簡體   English   中英

如何生成線程安全的統一隨機數?

[英]How do I generate thread-safe uniform random numbers?

我的程序需要在某個范圍內生成許多隨機整數(int min,int max)。 每次通話都有不同的范圍。 什么是好的(最好是線程安全的)方法呢? 以下不是線程安全的(並使用rand(),人們似乎不鼓勵):

int intRand(const int & min, const int & max)
{
    return (rand() % (max+1-min)) + min;
}

慢得多,但使用<random>

int intRand(const int & min, const int & max) {
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(min,max);
    return distribution(generator);
}

像這樣的東西就是我想要的(雖然changeParameters函數不存在):

int intRand(const int & min, const int & max) {
    static std::default_random_engine generator;
    static std::uniform_int_distribution<int> distribution(0, 10);
    distribution.changeParameters(min, max);
    return distribution(generator);
}

另一種選擇是在uniform_int_distribution上進行廣泛的范圍,然后在第一個例子中使用mod。 但是,我正在進行統計工作,所以我希望數字來自盡可能無偏差的分布(例如,如果使用的分布范圍不是(max-min)的倍數,則分布將略微偏置)。 這是一個選擇,但同樣,我想避免它。

解決方案此解決方案來自@ konrad-rudolph @ mark-ransom和@mathk的答案。 隨機數發生器的播種是為了滿足我的特殊需要。 更常見的方法是使用時間(NULL)。 如果你在同一秒內制作了很多線程,那么他們就會獲得相同的種子。 即使使用clock()也是一個問題,所以我們包含了線程ID。 缺點 - 這會泄漏內存 - 每個線程一個生成器。

#if defined (_MSC_VER)  // Visual studio
    #define thread_local __declspec( thread )
#elif defined (__GCC__) // GCC
    #define thread_local __thread
#endif

#include <random>
#include <time.h>
#include <thread>

using namespace std;

/* Thread-safe function that returns a random number between min and max (inclusive).
This function takes ~142% the time that calling rand() would take. For this extra
cost you get a better uniform distribution and thread-safety. */
int intRand(const int & min, const int & max) {
    static thread_local mt19937* generator = nullptr;
    if (!generator) generator = new mt19937(clock() + this_thread::get_id().hash());
    uniform_int_distribution<int> distribution(min, max);
    return distribution(*generator);
}

你試過這個嗎?

int intRand(const int & min, const int & max) {
    static thread_local std::mt19937 generator;
    std::uniform_int_distribution<int> distribution(min,max);
    return distribution(generator);
}

分布非常便宜(它們將由優化器完全內聯,因此唯一剩余的開銷是實際的隨機數重新縮放)。 不要害怕像你需要的那樣經常重新生成它們 - 事實上,重置它們在概念上並不便宜(這就是為什么不存在這種操作)。

另一方面,實際的隨機數生成器是一個承載很多狀態的重量級對象,需要相當長的時間來構造,因此每個線程只應初始化一次(或者甚至跨線程,但是那么你' d需要同步訪問,從長遠來看這是更昂貴的)。

使發生器static ,因此它只創建一次。 這是更有效的,因為好的發電機通常具有大的內部狀態; 更重要的是,它意味着您實際上獲得了它生成的偽隨機序列,而不是單獨序列的(隨機性更小的)初始值。

每次創建一個新的發行版; 這些通常是具有很少狀態的輕量級對象,尤其是像uniform_int_distribution一樣簡單的對象。

對於線程安全,選項是使生成器thread_local ,每個線程使用不同的種子,或使用互斥鎖保護它。 前者可能更快,特別是如果存在大量爭用,但會消耗更多內存。

您可以使用線程本地存儲為每個線程使用一個default_random_engine

我不能告訴你如何正確使用TLS,因為它依賴於操作系統。 您可以使用的最佳來源是通過互聯網進行搜索。

我是一個來自未來同樣問題的人。 接受的答案不會在MSVC 2013上編譯,因為它沒有實現thread_local(並且使用__declspec(thread)不起作用,因為它不喜歡構造函數)。

通過修改所有內容以使用新的位置,可以將解決方案中的內存泄漏移出堆。

這是我的解決方案(從頭文件和源文件組合):

#ifndef BUILD_COMPILER_MSVC
thread_local std::mt19937 _generator;
#else
__declspec(thread) char _generator_backing[sizeof(std::mt19937)];
__declspec(thread) std::mt19937* _generator;
#endif
template <typename type_float> inline type_float get_uniform(void) {
    std::uniform_real_distribution<type_float> distribution;
    #ifdef BUILD_COMPILER_MSVC
        static __declspec(thread) bool inited = false;
        if (!inited) {
            _generator = new(_generator_backing) std::mt19937();
            inited = true;
        }
        return distribution(*_generator);
    #else
        return distribution(_generator);
    #endif
}

為自己寫一個簡單的LCG(或其他)PRNG,這將產生最大可能需要的數字。 使用內置RNG的單個靜態副本為您生成的每個新線程為您自己的PRNG播種新的本地副本。 每個線程本地PRNG將具有其自己的本地存儲,並且永遠不需要再次引用中央RNG。

這假設統計上良好的RNG對您來說沒問題,並且加密安全性不是問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM