我有一个函数rand_double()
,它使用类型为std::mt19937
的 *static函数变量 * 生成一个随机浮点值。多个线程可以并发使用这个函数。
#include <random>
auto random_double(double min, double max) {
static std::mt19937 generator{std::random_device{}()}; // Forgot to seed it, thanks @user4581301
return std::uniform_real_distribution<>{min, max}(generator);
}
字符串
我的第一个问题是,上面的代码是线程安全的吗?我知道从C++11开始,静态函数变量的 * 初始化 * 总是线程安全的,但是并发使用生成器怎么样?
2条答案
按热度按时间vptzau2j1#
为了生成一个随机数,
std::uniform_real_distribution
调用参数生成器的operator()
。在mersenne twister的情况下,运算符具有副作用:
“发动机状态提前一个位置”
这不被(任何指示器)认为是线程安全操作。
您可以将其用作
thread_local
,这将在每个特定线程中产生预期的效果,即static
:字符串
px9o7tmv2#
thread_local
是要走的路OP问:
std::mt19937
静态函数变量是线程安全的吗?正如注解中所指出的,在
std::mt19937
对象上调用operator()
不是线程安全的。对于需要多次调用
std::mt19937
的应用程序,最好将引擎声明为thread_local
,这样每个线程都将获得自己的随机数引擎示例。使用随机数分布的
param_type
通过使
std::uniform_real_distribution
thread_local也可以稍微加快速度,这样就避免了每次生成随机数时不必要的构造函数调用。要做到这一点,在生成随机数时,必须向
uniform_real_distribution
提供param_type
对象以及std::mt19937
对象。在下面的函数中,
param_type{ min, max }
构造了一个临时param对象,并用min
和max
将其重命名。字符串
这是对OP问题最直接的解决办法。
作为替代方案,您可以通过在调用函数
random_double
之前构造param
对象来加快速度,并将其重用于多次调用。型
如果
min
和max
是常数会怎样?如果
min
和max
永远不会改变,那么可以通过硬编码来提高效率。这个版本的函数random_double
避免了传入参数min
和max
的开销。对于下面的示例,我将它们分别设置为1.0
和10.0
。型
如果需要闭合区间,请使用
std::next_after
C++标准要求
std::uniform_real_distribution
对象返回的随机数均匀分布在半开区间上,比如[a,b)。因此,它们大于或等于a
,小于(但永远不等于)b
。要创建闭区间[a,b]上的分布,
std::nextafter(b, std::numeric_limits<RealType>::max())
可以用作分布对象的构造函数的第二个参数。调用函数param
和operator()
调用函数也是如此。使用
std::random_device
播种std::mt19937
的最佳方法std::mt19937
和std::19937_64
都有19,968位的状态,所以试图用一个只有32或64位的种子来播种它们是愚蠢的。为了方便播种所有19,968位的状态,我编写了类
seed_seq_rd
。seed_seq_rd
代表 seed-sequence using random-device。这个类模仿std::seed_seq
的接口,但使用std::random_device
来生成种子。这种类型的对象是种子序列,可以用作随机数引擎中成员函数seed
的参数。型
seed_seq_rd
对象也可以用作随机数引擎的构造函数的参数,以及任何可以使用std:seed_seq
对象的地方。型
seed_seq_rd
的源代码在single-file header on GitHub中可用。它适用于任何可以用种子序列播种的随机数引擎,包括标准库中的所有引擎。类
seed_seq_rd
在头文件tbx.cpp14.seed_randomly.h
中定义。(它只使用C++14的特性,后来没有使用。因此得名。)使用它来播种std::mt19937
,将使您的函数更改为如下内容。型
输出量:
型