Последнее notify_all не вызывает последнюю условную_переменную. ожидание

Что я пытаюсь сделать

Привет, у меня есть два типа потоков: основной и рабочие, где рабочие равны количеству ядер на ЦП, что я пытаюсь сделать, так это когда основной поток должен вызвать обновление, я установил логическое значение, называемое Обновление до true и вызов condition_variable(cv).notify_all, тогда каждый поток выполнит свою работу, и когда это будет сделано, он увеличится на единицу atomic_int с именем CoresCompleted, за которым следует cv.notify_all, чтобы основной поток мог проверить, выполнена ли вся работа, затем он будет ждать переменной Updating быть ложным, чтобы быть уверенным, что все остальные потоки завершены, и он больше не обновляется, как только все будет сделано, основной поток устанавливает обновление в значение false и уведомляет всех.

КОД

Главный

void UpdateManager::Update() {

    //Prepare Update
    CoresCompleted = 0;
    Updating = true;

    //Notify Update Started
    cv.notify_all();

    //Wait for Update to end
    auto Pre = high_resolution_clock::now();
    cv.wait(lk, [] { return (int)UpdateManager::CoresCompleted >= (int)UpdateManager::ProcessorCount; });
    auto Now = high_resolution_clock::now();
    auto UpdateTime = duration_cast<nanoseconds>(Now - Pre);
    
    //End Update and nofity threads
    Updating = false;
    cv.notify_all();
}

Рабочие

void CoreGroup::Work() {

    Working = true;
    unique_lock<mutex> lk(UpdateManager::m);

    while (Working) {
        
        //Wait For Update To Start
        UpdateManager::cv.wait(lk, []{ return UpdateManager::Updating; });

        if (!Working)
            return;

        //Do Work
        size_t Size = Groups.size();

        auto Pre = high_resolution_clock::now();

        for (size_t Index = 0; Index < Size; Index++)
            Groups[Index]->Update();

        auto Now = high_resolution_clock::now();
        UpdateTime = duration_cast<nanoseconds>(Now - Pre);
        
        //Increment CoresCompleted And Notify All
        UpdateManager::CoresCompleted++;
        UpdateManager::cv.notify_all();

        //Wait For Update To End
        UpdateManager::cv.wait(lk, []{ return !UpdateManager::Updating; });
    }
}

Проблема

Как только рабочие достигают последнего ожидания, где они ждут, когда обновление будет ложным, они застревают и никогда не уходят, по какой-то причине последний notify_all в основном потоке не достигает рабочих, я пытался искать и искал много примеров, но я не могу' не могу понять, почему он не срабатывает, может быть, я не понял, как работают cv и lock, есть идеи, почему это происходит и как это исправить?

Вам нужно заблокировать UpdateManager::m, чтобы синхронизировать уведомление о событии, даже если Update оказывается атомарным. В противном случае условие может пропустить событие уведомления.

François Andrieux 05.04.2022 18:41

@FrançoisAndrieux, не могли бы вы объяснить, что именно вы подразумеваете под блокировкой m? Я пытался заблокировать перед установкой обновления, но он просто зависает в основном потоке, что именно мне делать? при поиске примеров в Интернете я никогда не видел, чтобы кто-то блокировал m

Pedro S 05.04.2022 19:07

См. en.cppreference.com/w/cpp/thread/condition_variable : «Даже если общая переменная является атомарной, она должна быть изменена в мьютексе, чтобы правильно опубликовать изменение в ожидающем потоке».UpdateManager::m должен быть заблокирован при каждом изменении Update. Вы можете разблокировать его между изменениями, чтобы потоки могли работать одновременно, но его необходимо снова заблокировать, прежде чем вносить в него дальнейшие изменения.

François Andrieux 05.04.2022 19:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
20
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Вот как работает ваш код: некоторое ожидание в Update закончилось, когда было получено уведомление:

cv.wait(lk, [] { return (int)UpdateManager::CoresCompleted >= (int)UpdateManager::ProcessorCount; });

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

cv.notify_all();

Но это ложь, они не могут продолжать работу, потому что вы держите замок. Отпустите его, и они продолжат работу:

void UpdateManager::Update() {
    <...>
    //End Update and nofity threads
    Updating = false;
    lk.unlock();
    cv.notify_all();
}

Вероятно, это не единственная проблема в этом коде, но я предполагаю, что вы блокируете мьютекс перед входом в метод Update или имеете некоторую гарантию, что он запустится раньше другого (Work).

Спасибо за ответ, так что раньше я не блокировался, но теперь я делаю это, я добавил lk.lock() в начале и разблокировал, где вы сказали, так что теперь он работает, но иногда он зависает с CoresCompleted, являющимся одним числом ниже ProcessorCount (15>=16) Я не уверен, почему это происходит, до использования метода уведомления этого никогда не происходило, так что это означает, что один из потоков не получает уведомление, есть идеи, почему это может быть?

Pedro S 05.04.2022 20:05

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

ixSci 05.04.2022 20:47

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