如何在C++11中使每个线程使用自己的随机数生成器(RNG)

7
我正在使用C++11中的新随机数生成器。虽然存在不同的意见,但从这个线程中可以看出大多数人认为它们不是线程安全的。因此,我想编写一个程序,使每个线程都使用自己的RNG。
在相关讨论中给出了一个使用OpenMP实现此目标的示例:
#include <random>
#include <iostream>
#include <time.h>
#include "omp.h"

using namespace std;



int main()
{
    unsigned long long app = 0;
    {
        //mt19937_64 engine((omp_get_thread_num() + 1)); //USE FOR MULTITHREADING
        mt19937_64 engine; //USE FOR SINGLE THREAD
        uniform_real_distribution<double> zeroToOne(0.0, 1.0);

        //#pragma omp parallel for reduction(+:app) //USE FOR MULTITHREADING
        for (unsigned long long i = 0; i < 2000000000; i++)
        {
            if(zeroToOne(engine) < 0.5) app++;
        }
    }
    cout << app << endl;
    return 0;
}

当我运行这个程序的多线程和单线程版本并跟踪时间时,它们在执行后完成所需的时间相同。此外,在这两种情况下,app 的大小不同,但我怀疑这仅仅是由于不同的种子造成的。
问题:提供的示例是否正确地显示了如何强制每个线程使用自己的 RNG?如果不是,能否给出一个示例说明如何实现,或者提供一个解释如何实现这一点的参考资料?
2个回答

6

您不能在多个线程之间共享随机引擎的实例。您应该锁定单个引擎或为每个线程创建一个引擎(使用不同的种子,请注意e4e5f4关于创建并行MT引擎的答案)。在OpenMP的情况下,您可以轻松地将一个引擎存储在向量中,并通过omp_get_thread_num()的结果来检索它,该结果介于0和omp_get_num_threads()–1之间。

class RNG
{
public:
    typedef std::mt19937 Engine;
    typedef std::uniform_real_distribution<double> Distribution;

    RNG() : engines(), distribution(0.0, 1.0)
    {
        int threads = std::max(1, omp_get_max_threads());
        for(int seed = 0; seed < threads; ++seed)
        {
            engines.push_back(Engine(seed));
        }
    }

    double operator()()
    {
        int id = omp_get_thread_num();
        return distribution(engines[id]);
    }

    std::vector<Engine> engines;
    Distribution distribution;
};

int main()
{
     RNG rand;
     unsigned long app = 0;

     #pragma omp parallel for reduction(+:app)
     for (unsigned long long i = 0; i < 2000000000; i++)
     {
         if(rand() < 0.5) app++;
     }
}

谢谢您提供的这个示例,非常有帮助。我有两个问题:(1) 我可以问一下为什么您选择包括 : engines() 吗?严格来说,这是必需的吗?...(2) 我能否在程序的后续循环中使用对象 rand,而该循环未并行化? - BillyJean
1
@BillyJean(1)不是必须的,但是我的个人风格是在初始化器列表中调用每个元素的构造函数,如果至少调用了一个。 (2)不是100%确定,但我认为omp_get_thread_num()对于非并行化区域返回0,所以是的。 - hansmaad
1
这是安全的。请注意,一些分布实现(如normal_distribution)可能会存储状态,因此也不能共享! - hansmaad
谢谢你提供的信息:但这正是我喜欢你的示例的原因,我可以轻松地将其扩展到库提供的任何分布。 - BillyJean
1
不需要锁。operator() 不会修改任何共享资源。它从一个向量中读取,如果没有并行写入同一向量,则始终是安全的。所有其他资源(从向量检索的引擎和可选地从向量检索的分布)都不是共享的。 - hansmaad
显示剩余3条评论

2

我建议不要使用随机种子,这可能导致流重叠。这最终会影响最终的统计数据。

我建议使用一些经过尝试和测试的解决方案,例如这个


2
你的“像这样经过试验的解决方案”会提醒:“注意:这还没有经过充分测试,所以可能包含许多错误。” :-) - hansmaad
我宁愿相信MTDC而不是随机种子 :) - Nishanth
当我在网站上看到这个注释时,我只是微笑了。然而,这份文件非常有趣。第一次实现并行PRNG时,我曾经想过如何为引擎提供种子,但我找不到任何信息。事实上,在使用简单的随机种子进行蒙特卡罗模拟时,我们从未遇到(明显的)问题。 - hansmaad

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