C ++ 11 Потокобезопасность генераторов случайных чисел - PullRequest
32 голосов
/ 11 января 2012

В C ++ 11 есть множество новых механизмов генерации случайных чисел и функций распределения.Они потокобезопасны?Если вы разделяете одно случайное распределение и механизм среди нескольких потоков, безопасно ли это, и вы все равно будете получать случайные числа?Сценарий, который я смотрю, выглядит примерно так:

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);
    }
}

с использованием OpenMP или

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);
    });
}

с использованием libdispatch.

Ответы [ 3 ]

26 голосов
/ 11 января 2012

Стандартная библиотека C ++ 11 является многопоточной. Гарантии безопасности потока на объектах PRNG такие же, как на контейнерах. Более конкретно, поскольку все классы PRNG являются псевдо -лучайными, то есть они генерируют детерминированную последовательность, основанную на определенном текущем состоянии, на самом деле нет места, чтобы заглядывать или тыкать во что-либо за пределами содержащегося состояния (которое также виден пользователю).

Точно так же, как контейнеры нуждаются в блокировках, чтобы сделать их безопасными для совместного использования, вам придется заблокировать объект PRNG. Это сделало бы это медленным и недетерминированным. Один объект на поток был бы лучше.

§17.6.5.9 [res.on.data.races]:

1 В этом разделе указываются требования, которым должны соответствовать реализации чтобы предотвратить гонки данных (1.10). Каждая стандартная библиотечная функция должна соответствовать каждому требованию, если не указано иное. Реализации могут предотвратить гонки данных в случаях, отличных от указанных ниже.

2 Стандартная библиотечная функция C ++ не должна прямо или косвенно доступ к объектам (1.10), доступным для потоков, отличных от текущего поток, если объекты не доступны прямо или косвенно через аргументы функции, включая эту.

3 Функция стандартной библиотеки C ++ не должна прямо или косвенно изменить объекты (1.10), доступные для потоков, отличных от текущего поток, если объекты не доступны прямо или косвенно через неконстантные аргументы функции, включая эту.

4 [Примечание: это означает, например, что реализации не могут использовать статический объект для внутренних целей без синхронизации, потому что он может вызвать гонку данных даже в программах, которые явно не разделяют объекты между нитями. -endnote]

5 Функция стандартной библиотеки C ++ не должна обращаться к объектам косвенно доступны через его аргументы или через элементы его контейнера аргументы за исключением вызова функций, требуемых его спецификацией на этих элементах контейнера.

6 Операции над итераторами, получаемые путем вызова стандартной библиотеки функция-член контейнера или строки может обращаться к контейнер, но не должен изменять его. [Примечание: в частности, контейнер операции, которые делают недействительными итераторы конфликтуют с операциями на итераторы, связанные с этим контейнером. - конец примечания]

7 Реализации могут совместно использовать свои внутренние объекты между потоками если объекты не видны пользователям и защищены от данных расы.

8 Если не указано иное, стандартные функции библиотеки C ++ должны выполнять все операции исключительно внутри текущего потока, если те Операции имеют видимые для пользователей эффекты (1.10).

9 [Примечание: это позволяет реализациям распараллеливать операции, если нет никаких видимых побочных эффектов. - конец примечания]

3 голосов
/ 11 января 2012

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

Кроме того, на самом деле я не вижу преимущества использования одного общего генератора случайных чисел вместо одного для каждого потока, каждый из которых слегка инициализируется по-разному (например, из результатов другого генератора или текущего идентификатора потока). В конце концов, вы, вероятно, не полагаетесь на генератор, генерирующий определенную последовательность при каждом запуске. Поэтому я бы переписал ваш код примерно так (для openmp, без понятия о 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);
        }
    }
}
1 голос
/ 11 января 2012

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

...