Рабочая нить навсегда останется в спящем режиме после слишком быстрого выполнения - PullRequest
0 голосов
/ 21 сентября 2018

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

Мой PeriodicThreads объект поддерживает коллекцию потоков.Как только PeriodicThreads::exec_threads() был вызван, потоки уведомляются, пробуждаются и выполняют свою задачу.После этого они снова засыпают.

Функция такого рабочего потока:

void PeriodicThreads::threadWork(size_t threadId){
    //not really used, but need to decalre to use conditional_variable:
    std::mutex mutex;
    std::unique_lock<std::mutex> lck(mutex);

    while (true){
        // wait until told to start working on a task:
        while (_thread_shouldWork[threadId] == false){
            _threads_startSignal.wait(lck);
        }

        thread_iteration(threadId);    //virtual function

        _thread_shouldWork[threadId] = false;   //vector of flags
        _thread_doneSignal.notify_all();

    }//end while(true) - run until terminated externally or this whole obj is deleted 
}

Как видите, каждый поток отслеживает свою собственную запись в векторе флагов, икак только он увидит, что его флаг истинен - ​​выполняет задачу, затем сбрасывает ее флаг.

Вот функция, которая может пробудить все потоки:

std::atomic_bool _threadsWorking =false;

//blocks the current thread until all worker threads have completed:
void PeriodicThreads::exec_threads(){
    if(_threadsWorking ){ 
        throw std::runtime_error("you requested exec_threads(), but threads haven't yet finished executing the previous task!");
    }

    _threadsWorking = true;//NOTICE: doing this after the exception check.

    //tell all threads to unpause by setting their flags to 'true'
    std::fill(_thread_shouldWork.begin(),  _thread_shouldWork.end(),  true);
    _threads_startSignal.notify_all();

    //wait for threads to complete:

    std::mutex mutex;
    std::unique_lock<std::mutex> lck(mutex); //lock & mutex are not really used.

    auto isContinueWaiting = [&]()->bool{
        bool threadsWorking = false; 
        for (size_t i=0;  i<_thread_shouldWork.size();  ++i){
            threadsWorking |= _thread_shouldWork[i];
        }
        return threadsWorking;
    };

    while (isContinueWaiting()){
        _thread_doneSignal.wait(lck);
    }

    _threadsWorking = false;//set atomic to false 
}

Вызов exec_threads() отлично работает длянесколько сотен или в редких случаях несколько тысяч последовательных итераций.Вызовы происходят из цикла while основного потока.Его рабочий поток обрабатывает задачу, сбрасывает ее флаг и переходит в спящий режим до следующего exec_threads() и т. Д.

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

Во время такой "гибернации" установка точки останова на любом while-loop моих condition_variables никогда не приводит к срабатыванию этой точки останова.


СуществоПодлый, я создал свой собственный ветвь проверки (параллельно main) и наблюдаю за моим PeriodicThreads объектом.Когда он входит в режим гибернации, мой контрольный поток продолжает выводить на консоль сообщение о том, что в настоящий момент ни один из потоков не запущен (для _threadsWorking atomic из PeriodicThreads постоянно установлено значение false).Тем не менее, во время других испытаний атом остается true, как только начинается эта «проблема гибернации».

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

Я завернул каждый condition_variable в цикл while, чтобы предотвратить случайные пробуждения от запуска перехода, и ситуацию, в которой notify_all вызывается до того, как .wait() вызывается на нем. Ссылка

Обратите внимание, это происходит, даже если у меня есть только 1 рабочий поток

В чем может быть причина?

Редактировать

Отмена этих векторных флагов и просто тестирование на одном atomic_bool с 1 рабочим потоком все еще показывает ту же проблему.

1 Ответ

0 голосов
/ 22 сентября 2018

Все общие данные должны быть защищены мьютексом.Мьютекс должен иметь (как минимум) ту же область, что и общие данные.

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

A condition_variable должен защищать фактическое условие (в этомВ этом случае состояние отдельного элемента _thread_shouldWork в любой заданной точке) и мьютекса используется для защиты переменных, охватывающих это условие.

Если вы просто используете случайный локальный мьютекс (как вынаходятся в вашем коде потока) или просто не используют мьютекс вообще (в основном коде), тогда все ставки отключены.Это неопределенное поведение.Хотя я мог видеть, что это работает (к счастью) большую часть времени.Я подозреваю, что происходит, что рабочий поток пропускает сигнал из основного потока.Также может быть, что ваш основной поток пропускает сигнал из рабочего потока.(Поток A читает состояние и входит в цикл while, затем Поток B изменяет состояние и отправляет уведомление, затем Поток A переходит в спящий режим ... в ожидании уже отправленного уведомления)

Мьютексыс локальной областью видимости - красный флаг!

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

Вы также должны следить за ложным обменом при использовании массивов

Редактировать: Вот видео, которое @Kari нашел полезным для объяснения ложного обменаhttps://www.youtube.com/watch?v=dznxqe1Uk3E

...