Кажется, здесь возникает пара проблем, связанных с этим.
Во-первых, не-поточно-безопасный характер функции rand()
означает, что одновременный вызов rand()
из разных потоков может привести к разным результатам.значения, чем если бы он был вызван последовательно.Вероятно, проще всего объяснить это на простом примере, поэтому давайте рассмотрим 32-битную версию PCG, поскольку она приятна и проста.Он имеет 32-битное состояние и генерирует 32-битные числа, например:
static uint32_t state = ...;
static uint32_t generate(void) {
uint32_t s = state;
uint32_t v = ((s >> ((s >> 28) + 4)) ^ s) * (277803737U);
v ^= v >> 22;
state = state * 747796405U + 1729U;
return v;
}
Теперь подумайте, что произойдет, если два потока вызовут generate()
примерно в одно и то же время.Возможно, они оба получают одинаковое значение для state
, и поэтому генерируют одно и то же случайное число дважды.Возможно, один обновляет state
до того, как другой прочитает его, поэтому они получают разные значения.
Мы можем устранить эту проблему, защищая функцию generate()
мьютексом или, в случае 32-битного PGC (и именно поэтому я использую это для воспроизводимых чисел), используя атомики.Если мы сделаем это, то мы всегда будем получать одинаковые числа в одном и том же порядке.
Вторая часть проблемы заключается в том, что происходит, когда порядок вызывающих абонентов смешивается в вашем коде.Допустим, у вас есть два потока (называемые A и B), и каждый из них должен выполнить две итерации вашего цикла.Даже если вы получаете ваши случайные числа из источника, ориентированного на многопотоковое исполнение, порядок вызовов может быть AABB, ABAB, ABBA, BBAA, BABA или BAAB, каждый из которых приведет к тому, что ваш код сгенерирует другой результат.
Есть несколько простых способов обойти это.Во-первых, вы можете использовать примитивы синхронизации, чтобы каждая итерация вызывала generate
в нужном вам порядке.Возможно, самый простой способ - использовать очередь, но вы тратите много времени на синхронизацию и теряете некоторые возможности для параллелизма (и вам придется значительно реструктурировать код).
Если у вас естьотносительно небольшое количество итераций, вы можете рассмотреть возможность создания массива заранее.Подумайте:
int i;
int nums[LEN];
for (i = 0 ; i < LEN ; i++)
nums[i] = generate();
#pragma omp parallel for ...
for (i = 0 ; i < LEN ; i++) {
do_stuff(nums[i]);
}
Лучшим решением, однако, может быть просто отказаться от идеи генерации случайных чисел и вместо этого использовать хеширование.https://burtleburtle.net/bob/hash/integer.html имеет несколько вариантов.Примером этого может быть что-то вроде
for (int i = 0 ; i < LEN ; i++) {
do_stuff(hash(i));
}
Конечно, вы можете добавить немного соли, возможно, даже использовать rand()
для получения соли.