Подтвердить что ты не робот

С++ 11 Безопасность потоков генераторов случайных чисел

В С++ 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.

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

Стандарт (хорошо 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);
        }
    }
}