考虑以下
rand_double()
的实现,其中有一个 thread_local
函数变量 seed
,在每个线程中使用 std::random_device{}()
进行初始化:
auto rand_double(double min, double max) {
thread_local uint64_t seed = std::random_device{}();
// ^^ Are the calls to std::random_device guaranteed to not interleave?
/* Generate random `double` and return it... */
}
我了解到静态函数变量的初始化保证是线程安全的。但是,我还没有找到一个来源来说明当使用 function 初始化
static
函数变量时,对该函数的调用是否不会交错。 有人可以确认使用函数来初始化static
(特别是thread_local
- 有区别吗?)函数变量是否是线程安全的?
让我们看看你在做什么:
std::random_device
作为自动(函数局部)变量。这是线程安全的。这是 C++ 标准的标准保证,请参阅第 16.5.5.10 节数据竞争避免 [res.on.data.races]
:2 C++ 标准库函数不得直接或间接访问当前线程以外的线程可访问的对象 (6.9.2),除非通过函数的参数直接或间接访问对象,包括
。this
3 C++ 标准库函数不得直接或间接修改当前线程以外的线程可访问的对象 (6.9.2),除非通过函数的非常量参数直接或间接访问对象,包括
。this
4 [注意:这意味着,例如,在没有同步的情况下,实现不能将静态对象用于内部目的,因为即使在线程之间未显式共享对象的程序中,它也可能导致数据争用。 — 尾注]
当前线程使用该随机设备来检索一个种子值。出于同样的原因,这是线程安全的。是的,当随机设备访问操作系统的共享熵池时,在幕后会发生一些同步,但这是库的工作,而不是您的工作。
您从该种子初始化一个线程本地对象。当然安全啦
你摧毁了随机设备。和上面一样
您的使用是线程安全的,因为每个线程都使用自己的
std::random_device
。
现在的问题是,这是一种明智的做事方式吗?我看到了两个问题:
这是基于该链接博客文章中的建议的替代方案,即为线程使用顺序种子值。
auto rand_double(double min, double max) {
static std::atomic<std::uint64_t> process_seed = std::random_device{}();
thread_local std::uint64_t seed =
process_seed.fetch_add(1, std::memory_order_relaxed);
}
或者通过不检查线程本地种子之前的
process_seed
来节省大约两条指令和每次调用一次跳转:
auto next_seed()
{
static std::atomic<std::uint64_t> process_seed = std::random_device{}();
return process_seed.fetch_add(1, std::memory_order_relaxed);
}
auto rand_double(double min, double max) {
thread_local std::uint64_t seed = next_seed();
}
这应该可以消除生日问题,并且还会降低每个新线程的成本。如果您运行多重处理(例如 MPI),您还应该将进程索引号纳入种子中。
此代码的一个潜在缺点是,使用一个线程的状态,您可以猜测相邻线程的随机状态。这在某些服务器应用程序中可能是一个问题,但对于其他应用程序(例如蒙特卡洛计算)来说,这似乎更合适。
旁注:
std::random_device::result_type
是一个简单的unsigned int
。你是零扩展的。如果您想要真正的 64 位种子,则需要进行多次调用。像这样的东西:
std::uint64_t random_device_seed()
{
std::uniform_int_distribution<std::uint64_t> distr; // full range
std::random_device rng;
return distr(rng);
}
并且,是的,只是为了澄清:出于与上面讨论的相同原因,像
static auto seed = random_device_seed()
或 thread_local auto seed = random_device_seed()
这样的东西将是线程安全的。