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

时间:2012-01-11 02:53:22

标签: 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> zeroToOne(0.0, 1.0);
#pragma omp parallel for
    for (int i = 0; i < 1000; i++) {
        double a = zeroToOne(engine);
    }
}

使用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);
    });
}

使用libdispatch。

3 个答案:

答案 0 :(得分: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)   除非通过直接或间接访问对象   function的非const参数,包括这个。

     

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

     

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

     

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

     

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

     

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

     

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

答案 1 :(得分:3)

标准(好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);
        }
    }
}

答案 2 :(得分:1)

documentation未提及线程安全性,因此我认为它们 线程安全。