2013-06-10 2 views
1

У меня есть простой пул потоков, который должен ждать, пока его рабочие будут потреблять все задачи в очереди, не используя стандартный thread::join(), так как я не хочу убивать сами темы но просто подождите, пока они завершат все задания, оставаясь в циклах выполнения. Я думал, что достичь этого с помощью condition_variable с, например, так:C++ 11 condition_variable проблема синхронизации

ThreadPool.cpp

void ThreadPool::addTask(ThreadTaskFunction task, void *arg) { 
    std::lock_guard<std::mutex> t_lock(task_lock); 
    tasks.push_back(ThreadTask(task, arg)); 

    // For every tasks that is added, I increment a counter 
    assignedTasks++; 
} 

void ThreadPool::join() { 
    std::unique_lock<std::mutex> lock(join_lock); 
    joinCondition.wait(lock, [this](){ 
     return assignedTasks == 0; 
    }); 
} 

Thread.cpp Обратите внимание, что это друг класс ThreadPool

void Thread::threadLoop() { 
    while (!shouldDie) { 
     ThreadTask task; 

     if (pool.hasTasks()) { 
      { 
       std::lock_guard<std::mutex> lock(pool.task_lock); 

       if (!pool.hasTasks()) continue; 

       task = pool.getTask(); 
      } 

      task.function(task.argument); 
      this->completeTask(); 
     } 
    } 

    this->isThreadFinished = true; 
} 

void Thread::completeTask() { 
    std::lock_guard<std::mutex> guard(pool.task_lock); 

    // When a task is completed, I decrement the counter and notify the main thread that a task has completed. 
    pool.assignedTasks--;  
    pool.joinCondition.notify_one(); 
} 

Я использую эту вещь для моделирования физики, и это происходит на каждом этапе моделирования (примерно раз каждые 16 мс). Случается, что после нескольких сотен шагов все останавливается, потому что какой-то сигнал уведомления отправляется до, главный поток входит в состояние ожидания, вероятно, потому, что он проверяет условие. Если я отлаживаю, я вижу, что у меня есть одна задача, оставленная на счетчике, никаких задач, выполняемых в потоках, нет задач в очереди, и ничего больше не происходит. Иногда, даже более странно, все разблокируется, как только я приостанавливаю выполнение с точкой останова и перезапускаю его. Я попытался поставить больше замков на место безрезультатно. Есть ли что-то очевидное, что мне не хватает?

UPDATE Оказывается, что эта проблема решается путем установки переменной assignedTasks в volatile. Он так часто изменяется, что иногда значение в регистрах не обновляется, и все останавливается. Никогда раньше это не делалось. Да. :)

+0

Присоединиться не убивает нити, он ждет их завершения – doctorlove

+0

Но я не хочу, чтобы они закончили, я просто хочу, чтобы они закончили свою задачу, а затем зациклились, ожидая большего. Я хочу, чтобы дождаться завершения всех текущих задач, чтобы я мог запланировать еще больше, не запустив новые потоки, но использую те, которые у меня уже есть. – juandemarco

ответ

1

Вы используете два различных мьютексы (join_lock, pool.task_lock), чтобы «защитить» assignedTasks в разное время - это обычная старомодная состояние гонки.

+0

Вы действительно правы, используя ту же блокировку (task_lock, например), решает проблему даже без «изменчивой» переменной. Мой вопрос тогда двоякий: 1. Почему/как изменчивость решала проблему? 2. Почему блокирование мьютекса в функции ThreadPool :: join() останавливает потоки от получения новых задач в Thread :: threadLoop()? – juandemarco

+2

1. Семантика C++ 'volatile' почти полностью определена. Для MSVC 'volatile T' имеет семантику получения-релиза, как C++ 11' std :: atomic '(См. Http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs. 110) .aspx). 2. Это семантика переменных условия: они блокируют блокировку при блокировке и повторно приобретают ее при проверке состояния - все «за кулисами» (см. Http://en.cppreference.com/w/cpp/thread/condition_variable). – Casey

+0

Спасибо, очень учебный! ;) – juandemarco

Смежные вопросы