Использование stdlib's rand () из нескольких потоков - PullRequest
40 голосов
/ 28 мая 2011

У меня есть несколько потоков, которые все выполняют одну и ту же функцию. В каждом из них они генерируют разные случайные числа несколько раз. Мы попытались сделать это, поставив srand(time(0)) в начале функции, но кажется, что все они получают одинаковое число.

Нужно ли вызывать srand(time(0)) только один раз для каждой программы, т.е. в начале main (например), в начале каждой функции, которая вызывается несколько раз, или что-то еще?

Ответы [ 7 ]

37 голосов
/ 28 мая 2011

srand () запускает генератор случайных чисел. Вам нужно всего лишь один раз вызвать srand(time(NULL)) во время запуска.

Тем не менее, документация гласит:

Функция rand() является не реентерабельной или потокобезопасный , поскольку он использует скрытый состояние, которое изменяется при каждом вызове. Это может быть просто начальное значение использоваться следующим вызовом, или это может быть чем-то более сложным. С целью чтобы получить воспроизводимое поведение в приложение с резьбой, это состояние должно быть явным Функция rand_r() поставляется с указателем на unsigned int, для использования в качестве состояния. Это очень небольшое количество государства, так что эта функция будет слабой псевдослучайный генератор. Пытаться drand48_r (3) вместо.

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

8 голосов
/ 08 ноября 2012

Если вы запускаете все потоки одновременно, время отправки в srand, вероятно, одинаково для каждого потока. Поскольку все они имеют одинаковое начальное число, они возвращают одинаковую последовательность. Попробуйте использовать что-то еще, например, адрес памяти из локальной переменной.

7 голосов
/ 28 мая 2011

Из справочной страницы rand:

Функция rand () не является реентерабельной или поточно-ориентированной, поскольку использует скрытое состояние, которое изменяется при каждом вызове.

Так что не используйте его с многопоточным кодом. Используйте rand_r (или drand48_r, если вы используете linux / glibc). Заполните каждый RNG другим значением (вы можете запустить первый RNG в главном потоке, чтобы получить случайные начальные числа для каждого из них в каждом потоке).

6 голосов
/ 05 ноября 2012

Поскольку вы используете C ++, а не C, вы можете избежать проблем с многопоточностью, часто связанных с srand / rand, используя c ++ 11. Это зависит от использования недавнего компилятора, который поддерживает эти функции. Вы бы использовали отдельный движок и дистрибутив в каждом потоке. Пример действует как игра в кости.

#include <random>
#include <functional>

std::uniform_int_distribution<int> dice_distribution(1, 6);
std::mt19937 random_number_engine; // pseudorandom number generator
auto dice_roller = std::bind(dice_distribution, random_number_engine);
int random_roll = dice_roller();  // Generate one of the integers 1,2,3,4,5,6.

Я сослался на Википедию C ++ 11 и Случайное повышение при ответе на этот вопрос.

2 голосов
/ 28 мая 2011

Хороший вопрос.Я не могу ответить прямо, потому что думаю, что есть более серьезные проблемы.Кажется, даже не ясно, что rand вообще безопасен для потоков.Он поддерживает внутреннее состояние, и он, кажется, не является четко определенным, если это для процесса или для потока, и если это для процесса, если это потокобезопасно.

Чтобы быть уверенным, я бы заблокировал мьютекс вокруг каждого доступа.

Или предпочтительно использовать более определенный генератор, например, один из boost

1 голос
/ 28 мая 2011

C не был разработан для многопоточности, поэтому поведение srand () с многопоточностью не определено и зависит от библиотеки времени выполнения C.

Многие библиотеки времени выполнения Unix / Linux C используют одно статическое состояние, которое не являетсябезопасный доступ из нескольких потоков, поэтому в этих средах выполнения C вы не можете использовать srand () и rand () из нескольких потоков вообще.Другие среды выполнения Unix C. могут вести себя по-разному.

Среда выполнения Visual C ++ использует внутреннее состояние для каждого потока, поэтому безопасно вызывать srand () для каждого потока.Но, как указал Нейл, вы, скорее всего, начнете заполнять все потоки с одинаковым значением - поэтому вместо этого начните с (time + thread-id).

Конечно, для переносимости используйте случайные объекты, а не функцию rand, а затемвы бы не зависели от скрытого состояния вообще.Вам все еще нужен один объект на поток, и заполнение каждого объекта с помощью (time + thread-id) все еще является хорошей идеей.

0 голосов
/ 28 мая 2011

Все они получают одно и то же число, потому что вы, вероятно, запускаете все потоки в одно и то же время, или они все используют один и тот же статический начальный размер, и в этом случае вы немного напичканы.Вам нужен лучший источник энтропии, чем время ().Однако, быстрый взлом состоял бы в том, чтобы заполнить (time * thread-id), где thread-id - это идентификатор каждого рабочего потока.

Конечно, правильное решение в C ++ - не использовать случайное числофункции генератора, но для использования объектов генератора случайных чисел, подобных тем, которые предоставляются библиотекой Boost для случайных чисел, которые по своей природе (потому что они основаны на стеке) являются поточно-ориентированными.См. этот ответ, который я подготовил ранее для примера.Тем не менее, все еще может быть проблема с обеспечением достаточной энтропии в программе MT, так как использование time () все еще будет иметь проблему, о которой я упоминал выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...