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

C++11 Thread safety of Random number generators

本文关键字:安全性 线程 随机数生成器 C++11      更新时间:2023-10-16

在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。

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[注意:如果没有明显的副作用。——尾注]

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

文档没有提到线程安全,所以我认为它们是而不是线程安全的。