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

use*_*069 32 c++ openmp thread-safety grand-central-dispatch c++11

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

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

使用libdispatch.

Pot*_*ter 26

C++ 11标准库具有广泛的线程安全性.PRNG对象上的线程安全保证与容器上的相同.更具体地说,由于PRNG类都是随机的,即它们基于确定的当前状态生成确定性序列,所以实际上没有空间在所包含状态之外的任何东西上偷看或戳戳(这对用户也是可见的) ).

就像容器需要锁来使它们安全共享一样,您必须锁定PRNG对象.这会使它变得缓慢且不确定.每个线程一个对象会更好.

§17.6.5.9[res.on.data.races]:

1本节规定了实现为防止数据竞争而应满足的要求(1.10).除非另有说明,否则每个标准库函数均应满足各项要求 在下面指定的情况下,实现可能会阻止数据争用.

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

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

4 [注意:这意味着,例如,实现不能在没有同步的情况下将静态对象用于内部目的,因为即使在未明确共享对象之间的对象的程序中,它也可能导致数据竞争.-endnote]

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

6通过调用标准库容器或字符串成员函数获得的迭代器操作可以访问底层容器,但不得修改它.[注意:特别是,使迭代器无效的容器操作与与该容器关联的迭代器上的操作冲突. - 结束说明]

7如果对象对用户不可见并且受到数据竞争保护,则实现可以在线程之间共享它们自己的内部对象.

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

9 [注意:如果没有可见的副作用,这允许实现并行化操作. - 结束说明]


Gri*_*zly 5

标准(好吧N3242)似乎没有提到随机数生成是免费的(除非不是rand),所以它不是(除非我错过了一些东西)。此外,让它们线程保存确实没有意义,因为它会产生相对较大的开销(至少与数字本身的生成相比),而不会真正赢得任何东西。

此外,我并没有真正看到拥有一个共享随机数生成器的好处,而不是每个线程都有一个,每个生成器的初始化略有不同(例如,根据另一个生成器的结果或当前线程ID)。毕竟,无论如何,您可能不依赖于每次运行生成特定序列的生成器。所以我会重写你的代码,如下所示(对于openmp,没有任何线索libdispatch):

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