web-dev-qa-db-fra.com

Comment générer des nombres aléatoires uniformes fil-safe?

Mon programme doit générer de nombreux entiers aléatoires dans une plage (int min, int max). Chaque appel aura une gamme différente. Qu'est-ce qu'un bon moyen (de préférence thread-safe) de le faire? Ce qui suit n'est pas thread-safe (et utilise Rand (), ce que les gens semblent décourager):

int intRand(const int & min, const int & max)
{
    return (Rand() % (max+1-min)) + min;
}

C'est beaucoup plus lent, mais utilise <random>:

int intRand(const int & min, const int & max) {
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(min,max);
    return distribution(generator);
}

Quelque chose comme ceci est ce que je vais pour (la fonction changeParameters n'existe pas cependant):

int intRand(const int & min, const int & max) {
    static std::default_random_engine generator;
    static std::uniform_int_distribution<int> distribution(0, 10);
    distribution.changeParameters(min, max);
    return distribution(generator);
}

Une autre option consisterait à créer une plage étendue sur le uniform_int_distribution, puis à utiliser mod comme dans le premier exemple. Cependant, je fais du travail statistique, donc je veux que les nombres proviennent d’une distribution aussi impartiale que possible (par exemple, si la plage de la distribution utilisée n’est pas un multiple de (max-min), la distribution sera légèrement biaisé). C'est une option, mais encore une fois, j'aimerais l'éviter.

SOLUTIONCette solution provient des réponses de @ konrad-rudolph @ mark-ransom et @mathk. Le classement du générateur de nombres aléatoires est fait pour répondre à mes besoins particuliers. Une approche plus courante consisterait à utiliser le temps (NULL). Si vous faites plusieurs threads dans la même seconde, ils auront alors la même graine. Même avec clock (), c'est un problème, nous incluons donc l'id du thread. Un inconvénient - cette fuite de mémoire --- un générateur par thread.

#if defined (_MSC_VER)  // Visual studio
    #define thread_local __declspec( thread )
#Elif defined (__GCC__) // GCC
    #define thread_local __thread
#endif

#include <random>
#include <time.h>
#include <thread>

using namespace std;

/* Thread-safe function that returns a random number between min and max (inclusive).
This function takes ~142% the time that calling Rand() would take. For this extra
cost you get a better uniform distribution and thread-safety. */
int intRand(const int & min, const int & max) {
    static thread_local mt19937* generator = nullptr;
    if (!generator) generator = new mt19937(clock() + this_thread::get_id().hash());
    uniform_int_distribution<int> distribution(min, max);
    return distribution(*generator);
}
24
PThomasCS

Avez-vous essayé cela?

int intRand(const int & min, const int & max) {
    static thread_local std::mt19937 generator;
    std::uniform_int_distribution<int> distribution(min,max);
    return distribution(generator);
}

Les distributions sont extrêmement peu coûteuses (elles seront complètement intégrées par l'optimiseur, de sorte que le seul temps système restant est le redimensionnement réel des nombres aléatoires). N'ayez pas peur de les régénérer aussi souvent que nécessaire - en fait, les réinitialiser ne serait théoriquement pas meilleur marché (c'est pourquoi cette opération n'existe pas).

Le générateur de nombres aléatoires, en revanche, est un objet lourd, porteur d’états multiples et nécessitant un certain temps pour être construit. Il ne doit donc être initialisé qu’une fois par thread (ou même par plusieurs threads). d besoin de synchroniser un accès plus coûteux à long terme).

26
Konrad Rudolph

Faites en sorte que le générateur static soit créé une seule fois. Ceci est plus efficace, car les bons générateurs ont généralement un grand état interne; plus important encore, cela signifie que vous obtenez réellement la séquence pseudo-aléatoire générée, et non les valeurs initiales (beaucoup moins aléatoires) de séquences séparées.

Créez une nouvelle distribution à chaque fois; ce sont généralement des objets légers avec peu d’état, en particulier un aussi simple que uniform_int_distribution.

Pour la sécurité des threads, les options sont de créer le générateur thread_local, avec un germe différent pour chaque thread, ou de le protéger avec un mutex. Le premier sera probablement plus rapide, surtout s'il y a beaucoup de conflits, mais consomme plus de mémoire.

4
Mike Seymour

Vous pouvez utiliser un default_random_engine par thread à l'aide de Thread Local Storage.

Je ne peux pas vous dire comment utiliser correctement TLS car il dépend du système d'exploitation. La meilleure source que vous pouvez utiliser est de rechercher sur Internet.

0
mathk

Je suis une personne du futur avec le même problème. La réponse acceptée ne sera pas compilée sur MSVC 2013, car elle n'implémente pas thread_local (et utiliser __declspec(thread) ne fonctionne pas car il n'aime pas les constructeurs).

La fuite de mémoire dans votre solution peut être déplacée du tas en modifiant tout pour utiliser le placement nouveau.

Voici ma solution (combinée à partir d'un en-tête et d'un fichier source):

#ifndef BUILD_COMPILER_MSVC
thread_local std::mt19937 _generator;
#else
__declspec(thread) char _generator_backing[sizeof(std::mt19937)];
__declspec(thread) std::mt19937* _generator;
#endif
template <typename type_float> inline type_float get_uniform(void) {
    std::uniform_real_distribution<type_float> distribution;
    #ifdef BUILD_COMPILER_MSVC
        static __declspec(thread) bool inited = false;
        if (!inited) {
            _generator = new(_generator_backing) std::mt19937();
            inited = true;
        }
        return distribution(*_generator);
    #else
        return distribution(_generator);
    #endif
}
0
imallett