web-dev-qa-db-fra.com

Utilisation de Rand () de stdlib à partir de plusieurs threads

J'ai plusieurs threads qui exécutent tous la même fonction. Dans chacun d'eux, ils génèrent plusieurs fois un nombre aléatoire différent. Nous avons essayé de le faire en mettant srand(time(0)) au début de la fonction, mais il semble qu'ils obtiennent tous le même numéro.

Faut-il appeler srand(time(0)) une seule fois par programme, c'est-à-dire au début de main (par exemple), au début de chaque fonction appelée plusieurs fois, ou autre chose?

40
SIMEL

srand () amorce le générateur de nombres aléatoires. Vous ne devriez avoir à appeler srand(time(NULL)) qu'une seule fois au démarrage.

Cela dit, la documentation indique:

La fonction Rand() n'est pas réentrante ou thread-safe , car elle utilise un état caché qui est modifié à chaque appel. Cela pourrait être juste la valeur de départ à utiliser par le prochain appel, ou ce pourrait être quelque chose de plus élaboré. Afin d'obtenir un comportement reproductible dans une application filetée, cet état doit être rendu explicite. La fonction Rand_r() est fournie avec un pointeur vers un unsigned int, à utiliser comme état. Il s'agit d'un très petit état, donc cette fonction sera un générateur pseudo-aléatoire faible. Essayez drand48_r (3) à la place.

La partie soulignée de ce qui précède est probablement la raison pour laquelle tous vos threads obtiennent le même numéro.

37

Depuis la page man Rand :

La fonction Rand () n'est pas réentrante ou thread-safe, car elle utilise un état caché qui est modifié à chaque appel.

Ne l'utilisez donc pas avec du code threadé. Utilisation Rand_r (ou drand48_r si vous êtes sous linux/glibc). Seed chaque RNG avec une valeur différente (vous pouvez amorcer un premier RNG dans le thread principal pour produire des graines aléatoires pour celles de chaque thread).

7
Mat

Comme vous utilisez C++, plutôt que C, vous pouvez éviter les problèmes de threads souvent associés à srand/Rand en utilisant c ++ 11. Cela dépend de l'utilisation d'un compilateur récent qui prend en charge ces fonctionnalités. Vous utiliseriez un moteur et une distribution séparés sur chaque thread. L'exemple agit comme un dé.

#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.

J'ai fait référence à Wikipedia C++ 11 et Boost random en répondant à cette question.

7
kshepherd

C'est une bonne question. Je ne peux pas y répondre directement car je pense qu'il y a des problèmes plus importants. Il ne semble même pas clair que Rand soit de toute façon sûr pour les threads. Il maintient l'état interne et il ne semble pas être bien défini si c'est par processus ou par thread, et si c'est par processus s'il est sûr pour les threads.

Pour être sûr, je verrouillerais un mutex autour de chaque accès.

Ou utilisez de préférence un générateur mieux défini tel que celui de boost

2
jcoder

C n'a pas été conçu pour le multithreading, donc le comportement de srand () avec le multithreading n'est pas défini et dépend de la bibliothèque d'exécution C.

De nombreuses bibliothèques d'exécution C Unix/Linux utilisent un état statique unique, ce qui n'est pas sûr d'accéder à partir de plusieurs threads, donc avec ces runtimes C, vous ne pouvez pas utiliser srand () et Rand () à partir de plusieurs threads. Les autres runtimes Unix C peuvent se comporter différemment.

Le runtime Visual C++ utilise l'état interne par thread, il est donc sûr d'appeler srand () pour chaque thread. Mais comme l'a souligné Neil, vous amorcerez probablement tous les threads avec la même valeur - donc amorcez avec (time + thread-id) à la place.

Bien sûr, pour la portabilité, utilisez des objets aléatoires plutôt que la fonction Rand, et vous ne dépendriez pas du tout de l'état caché. Vous avez toujours besoin d'un objet par thread, et l'amorçage de chaque objet avec (time + thread-id) est toujours une bonne idée.

1
Michael Entin