У меня возникли проблемы с пониманием условных переменных и их использования с мьютексами, надеюсь, сообщество поможет мне в этом. Обратите внимание, что я пришел из фона win32, поэтому я использую CRITICAL_SECTION, HANDLE, SetEvent, WaitForMultipleObject и т. Д.
Вот моя первая попытка параллелизма с использованием стандартной библиотеки c ++ 11, это модифицированная версия пример программы можно найти здесь.
#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>int _tmain(int argc, _TCHAR* argv[])
{
std::queue<unsigned int> nNumbers;
std::mutex mtxQueue;
std::condition_variable cvQueue;
bool m_bQueueLocked = false;
std::mutex mtxQuit;
std::condition_variable cvQuit;
bool m_bQuit = false;std::thread thrQuit(
[&]()
{
using namespace std;
this_thread::sleep_for(chrono::seconds(7));
// set event by setting the bool variable to true
// then notifying via the condition variable
m_bQuit = true;
cvQuit.notify_all();
}
);
std::thread thrProducer(
[&]()
{
using namespace std;
int nNum = 0;
unique_lock<mutex> lock( mtxQuit );
while( ( ! m_bQuit ) &&
( cvQuit.wait_for( lock, chrono::milliseconds(10) ) == cv_status::timeout ) )
{
nNum ++;
unique_lock<mutex> qLock(mtxQueue);
cout << "Produced: " << nNum << "\n";
nNumbers.push( nNum );
}
}
);
std::thread thrConsumer(
[&]()
{
using namespace std;
unique_lock<mutex> lock( mtxQuit );
while( ( ! m_bQuit ) &&
( cvQuit.wait_for( lock, chrono::milliseconds(10) ) == cv_status::timeout ) )
{
unique_lock<mutex> qLock(mtxQueue);
if( nNumbers.size() > 0 )
{
cout << "Consumed: " << nNumbers.front() << "\n";
nNumbers.pop();
}
}
}
);
thrQuit.join();
thrProducer.join();
thrConsumer.join();
return 0;
}
Несколько вопросов по этому поводу.
Я прочитал это msgstr «любой поток, который намерен ждать на std :: condition_variable, должен сначала получить std :: unique_lock.»
Итак, у меня есть {quit mutex, переменная условия & bool}, чтобы указать, когда о выходе было сообщено. Каждый из потоков производителей и потребителей должен получить std :: unique_lock следующим образом:
std::unique_lock<std::mutex> lock(m_mtxQuit);
Это запутывает меня до чертиков. Разве это не заблокирует выходной мьютекс в первом потоке, тем самым блокируя второй? И если это правда, то как первый поток снимает блокировку, чтобы другой поток мог начаться?
Другой вопрос: если я изменю вызов wait_for () на ожидание в течение нулевых секунд, этот поток будет голодать. Может кто-нибудь объяснить? Я ожидаю, что он не будет блокироваться перед выполнением цикла while (правильно ли я предположить, что no_timeout записывается вместо timeout?).
Как я могу вызвать wait_for () и указать нулевое время, чтобы вызов wait_for () не блокировался, а просто проверял условие и продолжал?
Мне также было бы интересно услышать о хороших ссылках на эту тему.
Разве это не заблокирует выходной мьютекс в первом потоке, тем самым блокируя второй?
Да.
И если это правда, то как первый поток снимает блокировку, чтобы другой поток мог начаться?
Когда вы ждете на condition_variable
он открывает замок, который вы передаете, так что в
cvQuit.wait_for( lock, chrono::milliseconds(10) )
переменная условия вызовет lock.unlock()
и затем блокировка на срок до 10 мс (это происходит атомарно, поэтому нет никакого промежутка между разблокировкой мьютекса и блокировкой, когда условие может быть готово, и вы его пропустите)
Когда мьютекс разблокирован, он позволяет другому потоку получить блокировку на нем.
Другой вопрос: если я изменю вызов wait_for () на ожидание в течение нулевых секунд, этот поток будет голодать. Может кто-нибудь объяснить?
Я бы ожидал Другой поток должен быть истощен, потому что мьютекс не разблокируется достаточно долго, чтобы другой поток мог его заблокировать.
правильно ли я предположить, что no_timeout является recv’d вместо timeout?
Нет, если время истекает без того, чтобы условие стало готовым, то оно «истекает» даже через ноль секунд.
Как я могу вызвать wait_for () и указать нулевое время, чтобы вызов wait_for () не блокировался, а просто проверял условие и продолжал?
Не используйте условную переменную! Если ты не хочешь Подождите чтобы условие стало истинным, не ждите переменную условия! Просто проверить m_bQuit
и продолжить.
(Кроме того, почему ваши логические значения называются m_bXxx
? Они не члены, поэтому m_
префикс вводит в заблуждение, а b
Приставка выглядит как та ужасная привычка MS венгерской нотации … которая воняет.)
Мне также было бы интересно услышать о хороших ссылках на эту тему.
Лучшая ссылка — Энтони Уильямс C ++ параллелизм в действии который подробно охватывает все атомарные и потоковые библиотеки C ++ 11, а также общие принципы многопоточного программирования. Одна из моих любимых книг на эту тему — Butenhof’s Программирование с помощью POSIX Threads, что характерно для Pthreads, но средства C ++ 11 очень тесно связаны с Pthreads, поэтому легко перенести информацию из этой книги в многопоточность C ++ 11.
Нотабене В thrQuit
ты пишешь в m_bQuit
без защиты мьютексом, поскольку ничто не мешает другому потоку читать его одновременно с записью, это условие гонки, то есть неопределенное поведение. Запись в bool должна быть либо защищена мьютексом, либо должна быть атомарного типа, например std::atomic<bool>
Я не думаю, что вам нужны два мьютекса, это только добавляет раздора. Так как вы никогда не выпускаете mtxQuit
кроме как в ожидании condition_variable
нет смысла иметь второй мьютекс, mtxQuit
один уже гарантирует, что только один поток может войти в критическую секцию одновременно.
Если вы хотите что-то проверить и продолжить независимо от того, является ли это истинным или нет (возможно, делать две разные вещи), тогда переменная условия не подходит для использования. Переменная условия — это низкоуровневый примитив для некоторого условия, связанного со структурой заблокированных данных, которую вы хотите ждать без необходимости раскручивать получение и снятие блокировки. Каноническим примером является очередь — у вас есть блокировка, защищающая доступ к очереди, и две переменные условия (очередь не пуста и очередь не заполнена). Чтобы вставить что-то в очередь, вы получаете блокировку, проверяете, что она не заполнена, ждите на незаполненном condvar, если он есть, выдвигаете значение в очереди, сигнализируете о непустом condvar (так как он больше не пуст) и снять замок. Поп-операция похожа.
Так что в вашем случае у вас есть простая очередь, которая не может быть заполнена, поэтому вам нужен один замок и один condvar для нее. Имеет смысл. Но тогда у вас есть флаг «Выход», который вы хотите, чтобы завершить триггер. Вы не хотите ждать, пока будет установлен флаг выхода — вы действительно хотите выполнять работу, пока он не будет установлен — поэтому condvar здесь действительно не имеет смысла. Да, вы МОЖЕТЕ придумать замысловатую договоренность, которая заставит его работать, но это может сбить с толку, так как в нем не используется условная переменная в качестве условной переменной.
Логичнее (и понятнее) просто использовать std::atomic<bool>
для флага выхода. Затем вы просто инициализируете значение false, установите значение true в свой поток выхода и проверьте его в других потоках.