Я пишу цикл событий, который переходит в спящий режим, когда нет работы, ожидая переменной условия «работа для выполнения» (work_to_do
). Эта условная переменная может быть уведомлена разными потоками на основе различных событий. Когда событие происходит в другом потоке, оно уведомляет переменную условия, пробуждая цикл событий, который затем проверяет условия, которые могли вызвать уведомление, зацикливается до тех пор, пока не закончится работа, а затем снова ждет. Одно из условий задается блокирующей функцией (WaitForMessage()
).
Поток цикла событий:
std::lock_guard<std::mutex> lock(work_to_do_lock);
for (;;) {
if (condition1) {
// Act on condition 1.
} else if (condition2) {
// Act on condition 2.
} else if (HasMessage()) {
// Act on receiving message.
} else {
work_to_do.wait(lock);
}
}
Поток, который обрабатывает уведомление от блокирующей функции:
for (;;) {
// Wait for message to be received (blocking). Once it returns you are
// guaranteed that HasMessage() will return true.
WaitForMessage();
// Wake-up the main event loop.
work_to_do.notify_one();
}
Основной поток блокирует мьютекс, защищающий условную переменную (work_to_do_lock
), перед входом в цикл событий и передает ее в вызов wait()
, когда нет работы. Чтобы избежать потери пробуждения, общий совет заключается в том, что все уведомители должны удерживать блокировку при обновлении своих состояний состояния. Однако, если бы вы защищали вызов WaitForMessage()
с помощью work_to_do_lock
, вы могли бы предотвратить пробуждение цикла событий другими сигналами.
Решение, которое я придумал, состоит в том, чтобы получить и снять блокировку после WaitForMessage()
, но до notify_one()
:
for (;;) {
// Wait for message to be received (blocking). Once it returns you are
// guaranteed that HasMessage() will return true.
WaitForMessage();
{
std::lock_guard<std::mutex> lock(work_to_do_lock);
}
// Wake-up the main event loop.
work_to_do.notify_one();
}
Это должно избежать проблемы с потерянным пробуждением, поскольку больше невозможно, чтобы условие стало истинным (WaitForMessage()
для возврата) и notify_one()
произошло между проверкой условия (HasMessage()
) и wait()
.
Альтернативный подход — не полагаться на HasMessage()
и просто обновить общую переменную, которую мы могли бы защитить с помощью блокировки:
for (;;) {
// Wait for message to be received (blocking). Once it returns you are
// guaranteed that HasMessage() will return true.
WaitForMessage();
{
std::lock_guard<std::mutex> lock(work_to_do_lock);
has_message = true;
}
// Wake-up the main event loop.
work_to_do.notify_one();
}
Соответствующий цикл обработки событий, проверяющий новый предикат условия:
std::lock_guard<std::mutex> lock(work_to_do_lock);
for (;;) {
if (condition1) {
// Act on condition 1.
} else if (condition2) {
// Act on condition 2.
} else if (has_message) {
has_message = false;
// Act on receiving message.
} else {
work_to_do.wait(lock);
}
}
Я никогда раньше не сталкивался с первым подходом, поэтому мне интересно, есть ли недостаток в дизайне или причина, по которой его обычно избегают? Кажется, вы могли бы использовать этот подход в качестве общей замены блокировки блокировка переменной условия перед обновлением состояния условия, предполагая, что запись/чтение конкретного состояния состояния само по себе защищено некоторым механизмом взаимного исключения.
condition_variable::wait
без предиката (и, возможно, без тайм-аута). Здесь — это какая-то статья, объясняющая эту проблему. Тогда вашим предикатом может быть какой-то атомарный флаг, установленный рабочими процессами, и у вас не должно возникнуть никаких проблем. - person pptaszni   schedule 14.01.2020if
/else if
, которые продолжаютelse
, где ожидается условная переменная (внутри безусловного цикла for, поэтому они всегда будут проверяться до и после ожидания). Пока эти два условия избегаются, вызовwait()
безопасен (даже без предиката или тайм-аута). - person JDN   schedule 14.01.2020WaitForMessage
выглядит нормально. Я предпочитаю установить (большой) тайм-аут, чтобы увидеть сообщение об ошибке, а не видеть, что мое приложение заблокировано в случае, если я совершу ошибку. В любом случае, просто подождите с предикатом и избавьте себя от неприятностей. - person pptaszni   schedule 14.01.2020wait()
, вы увидите, что он просто оборачивает обычное ожидание в цикл while, который проверяет функцию предиката. Ваше утверждение, в котором говорится, что в моем коде вы все еще повторно получаете блокировку, выполняете все условия, а затем снова вызываете ожидание, также является именно тем, что будет делатьwait()
с предикатом, нет никакой разницы в производительности. На самом деле, использование предиката приведет к пустой трате работы, так как мне в любом случае нужно явно проверить каждое условие, чтобы выполнить отправку. - person JDN   schedule 14.01.2020