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

PTh*_*sCS 29 c++ random multithreading

我的程序需要在某个范围内生成许多随机整数(int min,int max).每次通话都有不同的范围.什么是好的(最好是线程安全的)方法呢?以下不是线程安全的(并使用rand(),人们似乎不鼓励):

int intRand(const int & min, const int & max)
{
    return (rand() % (max+1-min)) + min;
}
Run Code Online (Sandbox Code Playgroud)

慢得多,但使用<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);
}
Run Code Online (Sandbox Code Playgroud)

像这样的东西就是我想要的(虽然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);
}
Run Code Online (Sandbox Code Playgroud)

另一个选择是uniform_int_distribution在第一个例子中使用mod然后使用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);
}
Run Code Online (Sandbox Code Playgroud)

Kon*_*lph 33

你试过这个吗?

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 对于这种方法,你应该确定种子引擎(例如使用`std :: random_device`)以避免不同的线程产生相同的数字序列. (4认同)
  • 你确定静态发生器是线程安全的吗? (3认同)
  • @PThomasCS也许是这样,但它产生了正确的结果.第一个没有.你使用`rand`的方式除了不是线程安全外,还有严重的偏见.MT19973是一个质量更高的随机数发生器(对于马尔可夫决策过程,您可能需要这种质量).使用`rand`(但质量仍然较差)的唯一正确方法是通过从一个分布中采样,该分布的大小是实际分布大小的整数倍,在一个循环中.这也不那么有效. (2认同)
  • @ggg正确,这是多余的(尽管合法)。 (2认同)

Mik*_*our 5

制作 generator static,所以它只创建一次。这更有效,因为好的生成器通常具有较大的内部状态;更重要的是,这意味着您实际上获得的是它生成的伪随机序列,而不是单独序列的(随机性低得多)初始值。

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

为了线程安全,选项是让 generatorthread_local为每个线程使用不同的种子,或者用互斥锁保护它。前者可能更快,尤其是在存在大量争用的情况下,但会消耗更多内存。

  • @mathk:它与任何其他标准语言功能一样可移植。如果您使用的编译器不支持它,则可能需要使用替代方案;但这会降低便携性,所以如果可能的话,我建议使用标准功能。 (4认同)
  • 您确定静态生成器是线程安全的吗?每个线程一个将是更好的选择。使用 TLS。 (3认同)