Как мне использовать rand_r и как его использовать в поточно-ориентированном виде? - PullRequest
21 голосов
/ 20 октября 2010

Я пытаюсь узнать, как использовать rand_r, и после прочтения этого вопроса Я все еще немного растерялся, может кто-нибудь взглянуть и указать, что мне не хватает? Насколько я понимаю, rand_r берет указатель на какое-то значение (или фрагмент памяти с некоторым начальным значением) и использует его для генерации новых чисел при каждом вызове. Каждый поток, который вызывает rand_r, должен снабдить его уникальным указателем (или частью памяти), чтобы получить «фактические случайные» числа между различными потоками. Вот почему это:

int globalSeed;

//thread 1
rand_r(&globalSeed);

//thread 2
rand_r(&globalSeed);

это неправильный способ его использования. Если у меня есть

int seed1,seed2;

//thread 1
rand_r(&seed1);

//thread 2
rand_r(&seed2);

это был бы правильный способ генерировать "истинные случайные" числа между потоками?


РЕДАКТИРОВАТЬ: дополнительные вопросы после прочтения ответов на вышеуказанную часть:

  1. если в теме 1 мне нужен случайный число от 1 до n, я должен сделать (rand_r(&seed1) % (n-1)) + 1? Или есть другой распространенный способ сделать это?
  2. Правильно или нормально, если память для семени выделяется динамически?

1 Ответ

15 голосов
/ 20 октября 2010

Это верно. В первом случае вы обходите потокобезопасный характер rand_r. Со многими не поточно-ориентированными функциями постоянное состояние сохраняется между вызовами этой функции (например, случайное начальное число здесь).

В поточно-ориентированном варианте вы фактически предоставляете поток данных (seed1 и seed2), чтобы гарантировать, что состояние не будет разделено между потоками.

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

В качестве примера, скажем, вы получаете случайную последовательность 2, 3, 5, 7, 11, 13, 17 с начальным начальным числом 0. С общим начальным числом чередующиеся вызовы rand_r из двух разных потоков вызовет это:

thread 1                thread 2
           <---  2
                 3 --->
           <---  5
                 7 --->
           <--- 11
                13 --->
           <--- 17

и это лучший случай - вы можете обнаружить, что общее состояние повреждено, поскольку его обновления могут быть не атомарными.

В состоянии без общего доступа (a и b представляют два разных источника случайных чисел):

thread 1                thread 2
           <---  2a
                 2b --->
           <---  3a
                 3b --->
           <---  5a
                 5b --->
                 ::

Некоторые поточно-ориентированные вызовы требуют, чтобы вы указывали состояние потока, подобное этому, другие могут создавать данные, относящиеся к потоку, под обложками (используя идентификатор потока или аналогичную информацию), чтобы вам никогда не приходилось об этом беспокоиться, Вы можете использовать один и тот же исходный код в многопоточных и непоточных средах. Я сам предпочитаю последнее, просто потому, что оно облегчает мою жизнь.


Дополнительные материалы для редактируемого вопроса:

> If in thread 1, I need a random number between 1 to n, should I do '(rand_r(&seed1) % (n-1)) + 1', or there is other common way of doing this?

Предполагая, что вы хотите значение от 1 до n включительно , используйте (rand_r(&seed1) % n) + 1. Первый бит дает значение от 0 до n-1 включительно, затем вы добавляете 1, чтобы получить желаемый диапазон.

> Is it right or normal if the memory for the seed is dynamically allocated?

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

Вы можете либо передать его через вызовы функций, либо как-нибудь настроить глобальный массив, чтобы нижние уровни могли найти правильный начальный адрес.

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

Вы, вероятно, (в обоих случаях использования глобального массива) имели бы структуру ключа, содержащую идентификатор потока в качестве ключа и начальное число для использования. Затем вам нужно будет написать свою собственную rand() подпрограмму, которая найдет правильное начальное число и с этим вызовет rand_r().

Это , поэтому я предпочитаю библиотечные процедуры, которые делают это под крышками с данными, специфичными для потоков.

...