c ++ генерирует потокобезопасные случайные числа - PullRequest
0 голосов
/ 01 октября 2019

Я прочитал несколько вопросов, похожих на тот, который я задаю, но ответы не кажутся мне полными или не совсем ясными.

Я пытаюсь распараллелить сканирование параметров, которое требует повторной генерации набора случайных чисел. Имея только один поток, я сейчас делаю что-то вроде этого:

int main() {
  //Get random number generators
  typedef std::mt19937 MyRNG;
  std::random_device rd;

  //seed generator
  MyRNG rng;
  rng.seed(rd());

  //make my uniform distributions for each parameter
  std::uniform_real_distribution<> param1(-1,1);
  std::uniform_real_distribution<> param2(-1,1);

  double x,y;
  //Do my scan
  for (int i = 0; i < N; i++) {
    x = param1(rng)
    y = param2(rng)

   //Do things with x and y*
  }

Таким образом, я получаю новые x и y для каждого сканирования. Теперь я хочу использовать несколько ядер, чтобы сделать это параллельно. Поэтому я перехожу к определению функции void scan(), которая по существу имеет то же содержимое, что и моя основная функция. Затем я создаю несколько потоков, и каждый из них запускает scan(). Но я не уверен, что это потокобезопасное использование std :: thread. Будет ли моя генерация случайных чисел в каждом потоке, как это в настоящее время является независимой? Могу ли я сэкономить время, создавая свои ГСЧ вне моей функции void? Благодарю.

1 Ответ

2 голосов
/ 01 октября 2019

Я бы, вероятно, сгенерировал бы seed in main и передал бы seed каждой функции потока. Я бы не стал использовать вывод std::random_device напрямую - я бы поместил числа в что-то вроде std::set или std::unordered_set, пока не получу столько семян, сколько хотел, чтобы убедиться, что я не дал двананизывает одно и то же семя (что, очевидно, было бы пустой тратой времени).

Что-то по этой общей линии:

int do_work(unsigned long long seed) {

  //Get random number generators
  typedef std::mt19937 MyRNG;

  //seed generator
  MyRNG rng(seed);

  //make my uniform distributions for each parameter
  std::uniform_real_distribution<> param1(-1,1);
  std::uniform_real_distribution<> param2(-1,1);

  double x,y;
  //Do my scan
  for (int i = 0; i < N; i++) {
    x = param1(rng);
    y = param2(rng);

   //Do things with x and y*
  }
}

static const int num_threads = 4;

int main() {
    std::set<unsigned long long> seeds;

    while (seeds.size() < num_threads)
       seeds.insert(std::random_device()());

    std::vector<std::thread> threads;

   for (auto const seed: seeds)
       threads.emplace_back(std::thread(do_work, seed));

    for (auto &t : threads)
        t.join();
}

В сторону, используя один результат из random_device для посеваstd::mt19937 довольно сильно ограничивает генератор - вы даете ему только 32 (или, возможно, 64) бита семян, но на самом деле он содержит 19937 бит исходного материала. std::seed_seq пытается улучшить это хотя бы до некоторой степени (среди прочего, вы можете использовать ряд выходных данных из std::random_device для создания семени.

О, и учитывая, что ваши два экземпляра uniform_real_distribution используйте одни и те же параметры, возможно, нет необходимости в двух отдельных объектах распространения.

...