Как генерировать случайные числа потокобезопасным способом с помощью 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 2

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(), что вызовет переполнение буфера жуки.

И, к сожалению, это решение предполагает детализацию реализации, которая не требуется стандартом, как объяснено в этом другом комментарии .