如何生成线程安全的统一随机数?

时间:2014-01-20 15:32:31

标签: c++ multithreading random

我的程序需要在某个范围内生成许多随机整数(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);
}

5 个答案:

答案 0 :(得分:30)

你试过这个吗?

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);
}

分发非常便宜(它们将由优化器完全内联,因此唯一剩余的开销是实际的随机数重新缩放)。不要害怕像你需要的那样经常重新生成它们 - 事实上,重置它们在概念上并不便宜(这就是为什么不存在这种操作的原因)。

另一方面,实际的随机数生成器是一个承载很多状态并且需要相当长时间构建的重量级对象,因此每个线程只应初始化一次(或者甚至跨线程,但是然后你需要同步访问权限,从长远来看这是更昂贵的。)

答案 1 :(得分:4)

创建生成器static,因此它只创建一次。这是更有效的,因为好的发电机通常具有大的内部状态;更重要的是,它意味着您实际上获得了它生成的伪随机序列,而不是单独序列的(更不那么随机的)初始值。

每次创建一个新的发行版;这些通常是状态很少的轻量级对象,尤其是像uniform_int_distribution这样简单的对象。

对于线程安全,选项是使生成器thread_local,每个线程使用不同的种子,或使用互斥锁保护它。前者可能更快,特别是如果有很多争用,但会消耗更多内存。

答案 2 :(得分:0)

您可以使用线程本地存储为每个线程使用一个default_random_engine

我无法告诉您如何正确使用TLS,因为它取决于操作系统。您可以使用的最佳来源是通过互联网进行搜索。

答案 3 :(得分:0)

我是来自未来的同样问题的人。接受的答案不会在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
}

答案 4 :(得分:-1)

为自己编写一个简单的LCG(或其他)PRNG,这将产生最多可能需要的数字。使用内置RNG的单个静态副本为您生成的每个新线程为您自己的PRNG播种新的本地副本。每个线程本地PRNG都有自己的本地存储,永远不需要再次引用中央RNG。

这假设统计上良好的RNG对您来说没问题,并且加密安全性不是问题。