Как генерировать случайные числа потокобезопасным способом с помощью OpenMP
До распараллеливания я создавал один объект default_random_engine
вне цикла, так как создание таких объектов не дешево. И я снова использовал его внутри петли.
При распараллеливании с OpenMP
я заметил, что uniform_dist(engine)
принимает изменяемую ссылку на случайный движок, который, как я предполагаю, не является потокобезопасным.
Программа не дает сбоев, но я беспокоюсь о ее корректности.
Я предполагаю, что random_device
является потокобезопасным, поэтому я мог бы просто переместить определение default_random_engine
внутри цикла, но я не хочу создавайте случайный объект двигателя каждую итерацию, так как я читал, что это не дешево.
default_random_engine
и использовать функции OpenMP для выбора правильного объекта в начале каждой итерации на основе идентификаторов потоков.
Есть ли лучший способ?
#include <iostream>
#include <random>
using namespace std;
int main() {
int N = 1000;
vector<int> v(N);
random_device r;
default_random_engine engine(r());
#pragma omp parallel for
for (int i = 0; i < N; ++i) {
uniform_int_distribution<int> uniform_dist(1, 100);
// Perform heavy calculations
v[i] = uniform_dist(engine); // I assume this is thread unsafe
}
return 0;
}
1 ответ:
Поскольку фактический код передает случайный движок многим функциям (каждая генерирует целые и вещественные числа из разных распределений), я пошел с массивом генератора на поток, потому что он налагает наименьшие изменения на кодовую базу:
Имейте в виду, что этот код предполагает, что функция#include <iostream> #include <omp.h> #include <vector> #include <random> using namespace std; int main() { random_device r; std::vector<std::default_random_engine> generators; for (int i = 0, N = omp_get_max_threads(); i < N; ++i) { generators.emplace_back(default_random_engine(r())); } int N = 1000; vector<int> v(N); #pragma omp parallel for for (int i = 0; i < N; ++i) { // Get the generator based on thread id default_random_engine& engine = generators[omp_get_thread_num()]; // Perform heavy calculations uniform_int_distribution<int> uniform_dist(1, 100); v[i] = uniform_dist(engine); // I assume this is thread unsafe } return 0; }
omp_set_num_threads
никогда не вызывается в программе. Если это произойдет, то потоки смогут получить числа (omp_get_thread_num()
) больше, чем старыеomp_get_max_threads()
, что вызовет переполнение буфера жуки.И, к сожалению, это решение предполагает детализацию реализации, которая не требуется стандартом, как объяснено в этом другом комментарии .