Есть ли лучший способ сделать этот код потокобезопасным? Thread_local static кажется тупым инструментом

Следующий код имитирует более крупную программу, которая создает экземпляр моделирования, а затем распараллеливает его с помощью firstprivate, чтобы сделать мои экземпляры приватными. Однако сам экземпляр создает в своих методах еще два экземпляра.

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

Он компилируется нормально, и при ручном тестировании кажется поточно-ориентированным и работающим.

Но я не уверен, что использую технологию C++ оптимальным образом, поскольку подозреваю, что могу объявить экземпляры выше в иерархии памяти и избавить себя от необходимости использовать переменную static, возможно, передав экземпляр, созданный в области parallel, по ссылке. в другой мой экземпляр или что-то в этом роде. Я подозреваю, что это лучше всего, потому что все в фигурных скобках #pragma omp parallel {} является локальным для потока.

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

Закомментированный код не работал, но вызывал «ошибку сегментации» при выходе программы с SIGSEGV 11.. Я считаю, что «уникальный» указатель не так уникален при параллельном развертывании. В целом это решение кажется более элегантным, и я хотел бы, чтобы оно работало, но я рад выслушать ваши комментарии.

Чтобы получить функциональность std::unique_ptr, необходимо закомментировать строку, начинающуюся с thread_local static, а остальные комментарии удалить.

#include <iostream>
#include <omp.h>
//#include <memory>

class GenNo
{
public:

    int num;

    explicit GenNo(int num_)
    {
        num = num_;
    };

    void create(int incr)
    {
        num += incr;
    }
};

class HelpCrunch{
public:
    HelpCrunch() {

    }

    void helper(int number)
    {
        std::cout << "Seed is " << number << " for thread number: " << omp_get_thread_num() << std::endl;
    }
};

class calculate : public HelpCrunch
{
public:

    int specific_seed;
    bool first_run;

    void CrunchManyNos()
    {
        HelpCrunch solver;

        thread_local static GenNo RanNo(specific_seed);
        //std::unique_ptr<GenNo> GenNo_ptr(nullptr);
        /*
        if (first_run == true)
        {
            GenNo_ptr.reset(new GenNo(specific_seed));
            first_run = false;
        }
         solver.helper(GenNo_ptr->num);
*/
        RanNo.create(1);
        solver.helper(RanNo.num);



        //do actual things that I hope are useful.
    };
};




int main()
{

    calculate MyLargeProb;
    MyLargeProb.first_run = true;

#pragma omp parallel firstprivate(MyLargeProb)
    {
        int thread_specific_seed = omp_get_thread_num();
        MyLargeProb.specific_seed = thread_specific_seed;

        #pragma omp for
        for(int i = 0; i < 10; i++)
        {
            MyLargeProb.CrunchManyNos();
            std::cout << "Current iteration is " << i << std::endl;
        }

    }
    return 0;
}

Теперь вывод с ключевым словом thread_local static был:

Seed is 2 for thread number: 1
Current iteration is 5
Seed is 3 for thread number: 1
Current iteration is 6
Seed is 4 for thread number: 1
Current iteration is 7
Seed is 5 for thread number: 1
Current iteration is 8
Seed is 6 for thread number: 1
Current iteration is 9


Seed is 1 for thread number: 0
Current iteration is 0
Seed is 2 for thread number: 0
Current iteration is 1
Seed is 3 for thread number: 0
Current iteration is 2
Seed is 4 for thread number: 0
Current iteration is 3
Seed is 5 for thread number: 0
Current iteration is 4

Не используя thread_local, но сохранив static, я получаю:

Seed is 2 for thread number: 1
Current iteration is 5
Seed is 3 for thread number: 1
Current iteration is 6
Seed is 4 for thread number: 1
Current iteration is 7
Seed is 6 for thread number: 1
Current iteration is 8
Seed is 7 for thread number: 1
Current iteration is 9


Seed is 5 for thread number: 0
Current iteration is 0
Seed is 8 for thread number: 0
Current iteration is 1
Seed is 9 for thread number: 0
Current iteration is 2
Seed is 10 for thread number: 0
Current iteration is 3
Seed is 11 for thread number: 0
Current iteration is 4

Если я вообще опущу ключевое слово static, экземпляр просто будет переназначен, и хотя я сильно подозреваю, что он будет оставаться приватным для потока, от него мало пользы, поскольку счетчик застрянет на 1 или 2 для потока 0 и 1 на двухъядерной машине. (Реальное приложение должно иметь возможность «подсчитывать» и не мешать параллельному потоку.)

С чем мне нужна помощь

Теперь я смоделировал пример таким образом, чтобы нарушение безопасности потоков стало очевидным из-за того, что счетчики мешают друг другу, как мы видим, это случай, когда thread_local не учитывается, но остается статический (отсутствие ни того, ни другого - глупо). Класс HelpCrunch на самом деле намного сложнее и, скорее всего, потокобезопасен, и его можно повторно инициализировать при каждом повторении цикла. (На самом деле это лучше, потому что он берет кучу переменных от своего потомка, который является частным экземпляром.) Но как вы думаете, мне лучше добавить thread_local к созданию solver, без ключевого слова static? Или я должен объявить экземпляр в другом месте, и в этом случае мне понадобится помощь с передачей по указателю / ссылке и т. д.

Почему бы просто не создать генератор для каждого потока и сохранить их в векторе (просто убедитесь, что они хранятся в памяти достаточно далеко друг от друга)?

Qubit 10.01.2019 12:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
224
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, ваш пример использует глобальный объект std::cout небезопасным для потоков способом, обращаясь к нему одновременно из нескольких потоков. Мне пришлось добавить #pragma omp critical в некоторых местах, чтобы получить читаемый результат.

Во-вторых, закомментированный код дает сбой, потому что GenNo_ptr имеет автоматическую продолжительность, поэтому он уничтожается каждый раз, когда CrunchManyNos() завершает выполнение. Следовательно, когда first_run - это false, вы отменяете защиту указателя nullptr.

Когда дело доходит до вашего конкретного вопроса, существует огромная разница между созданием RanNostatic или static thread_local:

  • Если это static, будет один экземпляр RanNo инициализируется при первом запуске CrunchManyNos(). Это чтобы в некоторой степени глобальная переменная, которая будет небезопасно использоваться в параллельный контекст вашего примера.

  • Если это static thread_local (кстати, используя openmp, вы должны предпочесть threadprivate), он будет будет создан в первый раз, когда новый поток вызывает CrunchManyNos() и будет длиться в течение всего потока.

большое спасибо! Видите ли вы способ использования указателя, чтобы избежать его удаления? Кроме того, где вы добавили критические утверждения? Результат был больше для того, чтобы увидеть, как работает программа. О статике и указателях, какой вариант лучше с точки зрения стиля?

Hirek 10.01.2019 14:57

Умные указатели @Hirek позволяют работать с ними как с автоматическими переменными. Следовательно, static thread_local std::unique_ptr<GenNo> GenNo_ptr подойдет. Однако в этом случае нет смысла использовать динамическое размещение.

metalfox 10.01.2019 16:27

@Hirek Я охранял каждое использование std::cout с #pragma omp critical, чтобы ваш пример работал. Но не используйте его в реальном коде, если это не является абсолютно необходимым, поскольку синхронизация потоков убьет производительность.

metalfox 10.01.2019 16:28

@Hirek Ваш код мне кажется подозрительным, потому что большинство ваших переменных прямо или косвенно зависят от идентификатора потока, хотя я не знаю вашего варианта использования и теоретически могу комментировать здесь только те вещи, которые не работают в вашем коде. В противном случае это материал Проверка кода.

metalfox 10.01.2019 16:29

да, я это вижу. Вариант использования намного больше, и я знаю, что использование идентификатора потока для параллельной генерации случайных чисел неуклюже и не так хорошо, как использование некоторого случайного устройства Unix для установки начального числа.

Hirek 10.01.2019 18:29

но это означало воспроизводимость при сохранении статистической независимости.

Hirek 10.01.2019 18:30

Позвольте нам продолжить обсуждение в чате.

Hirek 11.01.2019 11:08

Другие вопросы по теме