C++11 sécurité des threads des générateurs de nombres aléatoires

En C++11, Il y a un tas de nouveaux moteurs de générateur de nombres aléatoires et de fonctions de distribution. Sont-ils thread safe? Si vous partagez une seule distribution aléatoire et un moteur parmi plusieurs threads, est-ce sûr et recevrez-vous toujours des nombres aléatoires? Le scénario que je cherche est quelque chose comme,

void foo() {
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now())));
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0);
#pragma omp parallel for
    for (int i = 0; i < 1000; i++) {
        double a = zeroToOne(engine);
    }
}

En utilisant OpenMP ou

void foo() {
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now())));
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0);
    dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) {
        double a = zeroToOne(engine);
    });
}

Utilisation de libdispatch.

31
demandé sur ildjarn 2012-01-11 06:53:22

3 réponses

La bibliothèque standard C++11 est largement thread safe. Les garanties de sécurité du filetage sur les objets PRNG sont les mêmes que sur les conteneurs. Plus précisément, puisque les classes PRNG sont toutes pseudo -aléatoires, c'est-à-dire qu'elles génèrent une séquence déterministe basée sur un état actuel défini, il n'y a vraiment pas de place pour jeter un coup d'œil ou piquer quelque chose en dehors de l'état contenu (qui est également visible pour l'utilisateur).

Tout comme les conteneurs ont besoin de serrures pour les rendre sûrs à partager, vous auriez pour verrouiller L'objet PRNG. Cela le rendrait lent et non déterministe. Un objet par thread serait mieux.

§17.6.5.9 [res.on.données.les courses]:

1 cette section spécifie les exigences que les implémentations doivent respecter pour empêcher les courses de données (1.10). Chaque fonction de bibliothèque standard doit répondre à chaque exigence, sauf indication contraire. Les implémentations peuvent empêcher les courses de données dans des cas autres que ceux spécifiés ci-dessous.

2 une fonction de bibliothèque standard C++ ne doit pas, directement ou indirectement accéder aux objets (1.10) accessibles par des threads autres que thread sauf si les objets sont accessibles directement ou indirectement via le la fonction d'arguments, y compris le présent.

3 une fonction de bibliothèque standard C++ ne doit pas être directement ou indirectement modifier des objets (1.10) accessibles par des threads autres que thread sauf si les objets sont accessibles directement ou indirectement via le les arguments non-const de la fonction, y compris ceci.

4 [Note: cela signifie, par exemple, que les implémentations ne peuvent pas utiliser objet statique à des fins internes sans synchronisation car il pourrait provoquer une course de données même dans les programmes qui ne partagent pas explicitement objets betweenthreads. -note]

5 une fonction de bibliothèque standard C++ ne doit pas accéder indirectement aux objets accessible via ses arguments ou via des éléments de son conteneur arguments sauf en invoquant les fonctions requises par sa spécification sur ceux conteneur élément.

6 opérations sur les itérateurs obtenues en appelant une bibliothèque standard le conteneur ou la fonction de membre de chaîne peut accéder au sous-jacent conteneur, mais ne doit pas le modifier. [Note: en particulier, conteneur les opérations qui invalident les itérateurs entrent en conflit avec les opérations sur itérateurs associés à ce conteneur. - note de fin]

7 les implémentations peuvent partager leurs propres objets internes entre les threads si les objets ne sont pas visibles pour les utilisateurs et sont protégés par rapport aux données course.

8 sauf indication contraire, les fonctions de bibliothèque standard C++ doivent effectuer toutes les opérations uniquement dans le thread en cours si ceux-ci les opérations ont des effets visibles (1.10) Pour les utilisateurs.

9 [Note: cela permet aux implémentations de paralléliser les opérations si il n'y a pas visibles des effets secondaires. - note de fin]

25
répondu Potatoswatter 2012-01-11 08:28:29

La norme (Eh bien N3242) ne semble pas faire mention de la génération de nombres aléatoires sans race (sauf que rand ne l'est pas), donc ce n'est pas le cas (sauf si j'ai manqué quelque chose). En outre, il est vraiment inutile de les avoir threadsave, car cela entraînerait une surcharge relativement lourde (par rapport à la génération des nombres eux-mêmes au moins), sans vraiment gagner quoi que ce soit.

En outre, Je ne vois pas vraiment un avantage og ayant un générateur de nombres aléatoires partagés, au lieu d'en avoir un par thread, chacun étant légèrement initialisé différemment (par exemple à partir des résultats d'un autre générateur, ou de l'id de thread actuel). Après tout, vous ne comptez probablement pas sur le générateur générant une certaine séquence chaque exécution de toute façon. Donc, je réécrirais votre code comme quelque chose comme ça (pour openmp, aucune idée de libdispatch):

void foo() {
    #pragma omp parallel
    {
    //just an example, not sure if that is a good way too seed the generation
    //but the principle should be clear
    std::mt19937_64 engine((omp_get_thread_num() + 1) * static_cast<uint64_t>(system_clock::to_time_t(system_clock::now())));
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0);
    #pragma omp for
        for (int i = 0; i < 1000; i++) {
            double a = zeroToOne(engine);
        }
    }
}
3
répondu Grizzly 2012-01-11 03:22:57

La documentation ne fait aucune mention de la sécurité des threads, donc je suppose qu'ils le sont non fil sûr.

0
répondu Mitch Wheat 2012-01-11 02:58:50