Mersenne Twister在C++中是线程安全的吗?

11
#include <random>

int f() {

    std::random_device seeder;
    std::mt19937 engine(seeder());
    std::uniform_int_distribution<int> dist(1, 6);

    return dist(engine);

}

多个线程可以安全地调用这个函数吗?这个函数是线程安全的吗? 每次调用std::random_device seeder;std::mt19937 engine(seeder());是多余的吗?


2
为什么要使用“C”标签?这与C语言无关。 - UnholySheep
我真的不明白为什么我收到了负评。我在问这个是否是线程安全的,以及是否需要重新生成种子。 - cateof
1
可能是如何生成线程安全的均匀随机数?的重复。 - Michael Foukarakis
你分享的链接并没有解决我的问题。我尝试了这段代码。 - cateof
问题已更新。 - cateof
显示剩余3条评论
2个回答

17

没有任何一个C++的std类型以非线程安全的方式使用全局数据。两个不相关类型的实例可以在不同的线程中被访问。

默认情况下,一个类型的实例在没有同步的情况下不能从两个线程中被访问。

您创建了本地变量,这些本地变量与其类型的任何其他实例无关。这里没有任何线程安全问题。

伪随机值最有效地通过具有状态并重复使用它来产生。您没有这样做,因此生成1到6之间的随机数将相对昂贵。

std::random_device seeder;
std::mt19937 engine(seeder());
std::uniform_int_distribution<int> dist(1, 6);
return dist(engine);

您使用的std::mt19937是多余的。您已经创建了一个random_device,它可以直接提供给dist,然后从中创建一个engine,然后使用engine。这里使用engine是无用的。

传统上,您会从seeder创建一个engine(某种类型的,如mt19937),然后将其存储,并重复将其传递给分布。

这样做会进行相对昂贵的“真随机数”生成一次,通过引擎通过分布生成一系列伪随机数。

但是请注意,这种使用有成本;您必须存储engine,并且必须防止多线程访问它。

正确的方法是拥有一个为您生成随机值的对象,并在需要时传递它。存储使用的初始种子也将允许您重复涉及的一组随机数字的执行。

如果您不喜欢明确传递随机状态的想法,则可以使用thread_local(或带有mutex保护的static)。

thread_local std::mt19937 engine(std::random_device{}());
std::uniform_int_distribution<int> dist(1, 6);
return dist(engine);

这将为每个线程创建一个 engine,并使用来自您的 random_device 的值初始化该 engine


4
这个特定的函数是线程安全的。在多个线程中创建随机数生成器、引擎和分布,并在函数本地引擎中调用生成数字是可以的。
当然,由于cout未同步,多个线程的输出可能会交错。
你需要每次在函数调用中初始化引擎吗?那就是你的函数所做的事情,虽然这确保了线程安全,但与你需要做的相反。每次初始化引擎都将使“随机性”序列直接依赖于种子。而且它还增加了初始化引擎的开销。
或者把前两行(种子和引擎)放到类构造函数中会更好吗?你可以使用一个包装类,但不一定要这样做。这与是否在每个函数调用中创建新的引擎实例无关。只要每个函数调用使用之前的引擎相同,就不会在这方面对随机性造成问题。
但是,在跨线程使用相同的引擎确实不是线程安全的。你可以在每个线程中使用一个引擎,或者使用互斥锁保护共享引擎,但这会带来显着的开销。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接