C++ C++;11随机数生成器的线程安全

C++ C++;11随机数生成器的线程安全,c++,thread-safety,c++11,openmp,grand-central-dispatch,C++,Thread Safety,C++11,Openmp,Grand Central Dispatch,在C++11中,有许多新的随机数生成器引擎和分布函数。它们是线程安全的吗?如果您在多个线程之间共享一个随机分布和引擎,它安全吗?您还会收到随机数吗?我所看到的场景是这样的 void foo() { std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now()))); std::uniform_real_distribution<double>

在C++11中,有许多新的随机数生成器引擎和分布函数。它们是线程安全的吗?如果您在多个线程之间共享一个随机分布和引擎,它安全吗?您还会收到随机数吗?我所看到的场景是这样的

void foo() {
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now())));
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0);
#pragma omp parallel for
    for (int i = 0; i < 1000; i++) {
        double a = zeroToOne(engine);
    }
}
void foo(){
std::mt19937_64引擎(静态转换(系统时钟::到时间(系统时钟::现在()));
标准:均匀实分布零通(0.0,1.0);
#pragma-omp并行
对于(int i=0;i<1000;i++){
双a=零通(发动机);
}
}
使用OpenMP或

void foo() {
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now())));
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0);
    dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) {
        double a = zeroToOne(engine);
    });
}
void foo(){
std::mt19937_64引擎(静态转换(系统时钟::到时间(系统时钟::现在()));
标准:均匀实分布零通(0.0,1.0);
调度应用(1000,调度获取全局队列(调度队列优先级高,0),^(大小i){
双a=零通(发动机);
});
}
使用libdispatch。

没有提到线程安全,所以我假设它们不是线程安全。

标准(好的
N3242
)似乎没有提到随机数生成是无种族的(除了
rand
不是),所以它不是(除非我错过了什么)。此外,让他们使用threadsave也没有什么意义,因为它会带来相对巨大的开销(至少与生成数字本身相比),而不会真正赢得任何东西

此外,我并不认为og有一个共享随机数生成器的好处,而不是每个线程都有一个,每个线程的初始化方式略有不同(例如,来自另一个生成器的结果或当前线程id)。毕竟,您可能并不依赖于生成器生成特定的序列。因此,我会将您的代码重写为如下内容(对于
openmp
,对
libdispatch
)没有任何线索:

void foo(){
#pragma-omp并行
{
//只是一个例子,我不确定这是否是一个好办法,也种子的一代
//但原则应该是明确的
std::mt19937_64引擎((omp_get_thread_num()+1)*静态转换(系统时钟::到时间(系统时钟::现在()));
标准:均匀实分布零通(0.0,1.0);
#pragma omp for
对于(int i=0;i<1000;i++){
双a=零通(发动机);
}
}
}

C++11标准库基本上是线程安全的。PRNG对象上的线程安全保证与容器上的线程安全保证相同。更具体地说,由于PRNG类都是伪随机的,即它们基于确定的当前状态生成确定性序列,因此实际上没有空间窥探或戳到包含状态(用户也可以看到)之外的任何内容

正如容器需要锁来确保安全共享一样,您也必须锁定PRNG对象。这将使它变得缓慢和不确定。每个线程一个对象会更好

§17.6.5.9[关于数据竞赛的决议]:

1本节规定了实施应满足的要求 防止数据争用(1.10)。每个标准库函数都应 除非另有规定,否则应满足各项要求。实施可能 在下列情况以外的情况下防止数据争用

2 C++标准库函数不应直接或间接 访问对象(1.10),该对象可由当前线程以外的线程访问 线程,除非通过直接或间接访问对象 函数的参数,包括此参数

3 C++标准库函数不应直接或间接 修改除当前线程之外的线程可访问的对象(1.10) 线程,除非通过直接或间接访问对象 函数的非常量参数,包括以下参数

4[注:例如,这意味着实现不能使用 用于内部目的的静态对象,而不进行同步,因为 即使在没有显式共享的程序中,也可能导致数据竞争 读之间的对象。-尾注]

5:C++标准库函数不应间接访问对象 可通过其参数或其容器的元素访问 参数,除非调用其规范所需的函数 在这些容器元素上

通过调用标准库获得的迭代器上的6个操作 容器或字符串成员函数可以访问底层 容器,但不得对其进行修改。[注:特别是集装箱 使迭代器无效的操作与上的操作冲突 与该容器关联的迭代器。-结束注释]

7实现可以在线程之间共享自己的内部对象 如果对象对用户不可见并且受到数据保护 比赛

8除非另有说明,C++标准库函数应 仅在当前线程内执行所有操作,如果 操作具有对用户可见的效果(1.10)

9[注:这允许实现在以下情况下并行化操作: 没有明显的副作用。-结束语]


没有在cppreference.com上提及并不意味着这样。实际上,如果从不同线程读取相同的RNG,则即使对于固定种子,也不能依赖于获得相同的随机数序列,因为调度可能会导致不同线程在单独运行时对RNG的访问顺序不同。因此,特别是如果您需要可复制的随机数序列,则不应在线程之间共享RNG。@celtschk:这取决于如何定义获取相同序列。我想说的是,一个线程会得到相同的序列(globaly),只是每次运行时线程会看到它的不同部分。这给了我一个很好的起点!对于obs,最好指定一个种子,而不是使用系统时间+日期(如果您关心
void foo() {
    #pragma omp parallel
    {
    //just an example, not sure if that is a good way too seed the generation
    //but the principle should be clear
    std::mt19937_64 engine((omp_get_thread_num() + 1) * static_cast<uint64_t>(system_clock::to_time_t(system_clock::now())));
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0);
    #pragma omp for
        for (int i = 0; i < 1000; i++) {
            double a = zeroToOne(engine);
        }
    }
}