我有很多线程,每个线程都需要一个线程安全的随机数。由于在我的实际程序中线程是反复生成和连接的,所以每次输入一个调用相同函数的新并行区域时,我都不想创建random_device
和mt19937
,所以我将它们作为静态的:
#include <iostream>
#include <random>
#include <omp.h>
void test(void) {
static std::random_device rd;
static std::mt19937 rng(rd());
static std::uniform_int_distribution<int> uni(1, 1000);
int x = uni(rng);
# pragma omp critical
std::cout << "thread " << omp_get_thread_num() << " | x = " << x << std::endl;
}
int main() {
# pragma omp parallel num_threads(4)
test();
}
我不能将它们作为threadprivate
,因为错误C3057:当前不支持“线程私有”符号的动态初始化。一些消息来源说random_device
和mt19937
是线程安全的,但是我还没有找到任何能证明这一点的文档。
发布于 2022-05-03 14:56:17
这里有一种不同的方法。我保留了一个全局种子值,以便只使用random_device
一次。由于使用它的速度可能很慢,我认为谨慎的做法是尽量少使用它。
相反,我们增加了每个线程的种子值,也增加了每次使用的种子值。这样我们就避免了生辰悖论,并将线程-局部状态最小化为一个整数。
#include <omp.h>
#include <algorithm>
#include <array>
#include <random>
using seed_type = std::array<std::mt19937::result_type, std::mt19937::state_size>;
namespace {
seed_type init_seed()
{
seed_type rtrn;
std::random_device rdev;
std::generate(rtrn.begin(), rtrn.end(), std::ref(rdev));
return rtrn;
}
}
/**
* Provides a process-global random seeding value
*
* Thread-safe (assuming the C++ compiler if standard-conforming.
* Seed is initialized on first call
*/
seed_type global_seed()
{
static seed_type rtrn = init_seed();
return rtrn;
}
/**
* Creates a new random number generator
*
* Operation is thread-safe, Each thread will get its own RNG with a different
* seed. Repeated calls within a thread will create different RNGs, too.
*/
std::mt19937 make_rng()
{
static std::mt19937::result_type sequence_number = 0;
# pragma omp threadprivate(sequence_number)
seed_type seed = global_seed();
static_assert(seed.size() >= 3);
seed[0] += sequence_number++;
seed[1] += static_cast<std::mt19937::result_type>(omp_get_thread_num());
seed[2] += static_cast<std::mt19937::result_type>(omp_get_level());
std::seed_seq sseq(seed.begin(), seed.end());
return std::mt19937(sseq);
}
还请参见:How to make this code thread safe with openMP? Monte Carlo two-dimensional integration
有关只增加种子值的方法,请参见以下内容:https://www.johndcook.com/blog/2016/01/29/random-number-generator-seed-mistakes/
发布于 2022-05-03 14:05:01
我认为threadprivate
仍然是正确的方法,您可以通过稍后执行并行分配来避免初始化问题。
static random_device rd;
static mt19937 rng;
#pragma omp threadprivate(rd)
#pragma omp threadprivate(rng)
int main() {
#pragma omp parallel
rng = mt19937(rd());
#pragma omp parallel
{
stringstream res;
uniform_int_distribution<int> uni(1, 100);
res << "Thread " << omp_get_thread_num() << ": " << uni(rng) << "\n";
cout << res.str();
}
return 0;
}
顺便说一下,注意stringstream
:OpenMP倾向于在<<
操作符上拆分输出行。
https://stackoverflow.com/questions/72098550
复制相似问题