Диагностическое правило основано на пункте CP.42 CppCoreGuidelines.
Анализатор обнаружил ситуацию, в которой одна из нестатических функций-членов класса 'std::condition_variable' – 'wait', 'wait_for' или 'wait_until' – вызывается без предиката. Это может привести к проблемам: ложному пробуждению потока или его зависанию.
Рассмотрим пример N1, приводящий к потенциальному зависанию:
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> std::mutex mtx; std::condition_variable cond; void consumer() { std::unique_lock<std::mutex> lck { mtx }; std::cout << "Waiting... " << std::endl; cond.wait(lck); // <= std::cout << "Working..." << std::endl; } void producer() { { std::lock_guard<std::mutex> _ { mtx }; std::cout << "Preparing..." << std::endl; } cond.notify_one(); } int main() { std::thread c { consumer }; std::thread p { producer }; c.join(); p.join(); }
В примере есть состояние гонки. Программа может зависнуть, если она выполнится в следующем порядке:
Для исправления следует модифицировать код следующим образом:
Исправленный пример:
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> std::mutex mtx; std::condition_variable cond; bool pendingForWorking = false; // <= void consumer() { std::unique_lock<std::mutex> lck { mtx }; std::cout << "Waiting... " << std::endl; cond.wait(lck, [] { return pendingForWorking; }); // <= std::cout << "Working..." << std::endl; } void producer() { { std::lock_guard<std::mutex> _ { mtx }; pendingForWorking = true; // <= std::cout << "Preparing..." << std::endl; } cond.notify_one(); } int main() { std::thread c { consumer }; std::thread p { producer }; c.join(); p.join(); }
Рассмотрим пример N2, в котором может произойти ложное пробуждение:
#include <iostream> #include <fstream> #include <sstream> #include <queue> #include <thread> #include <mutex> #include <condition_variable> std::queue<int> queue; std::mutex mtx; std::condition_variable cond; void do_smth(int); void consumer() { while (true) { int var; { using namespace std::literals; std::unique_lock<std::mutex> lck { mtx }; if (cond.wait_for(lck, 10s) == std::cv_status::timeout) // <= { break; } var = queue.front(); queue.pop(); } do_smth(var); } } void producer(std::istream &in) { int var; while (in >> var) { { std::lock_guard<std::mutex> _ { mtx }; queue.push(var); } cond.notify_one(); } } void foo(std::ifstream &fin, std::istringstream &sin) { std::thread p1 { &producer, std::ref(fin) }; std::thread p2 { &producer, std::ref(sin) }; std::thread p3 { &producer, std::ref(std::cin) }; std::thread c1 { &consumer }; std::thread c2 { &consumer }; std::thread c3 { &consumer }; p1.join(); p2.join(); p3.join(); c1.join(); c2.join(); c3.join(); }
Ложное пробуждение – явление, при котором ожидающий поток пробуждается и обнаруживает, что условие, которое он ожидал, не выполнено. Это может произойти в двух сценариях:
В примере N2 ложное пробуждение может произойти в потоках 'c1', 'c2' и 'c3'. В результате такого пробуждения очередь может оказаться пустой, и доступ к ней приведёт к неопределенному поведению.
Для исправления следует также вызвать перегрузку 'std::condition_variable::wait_for', принимающую предикат. Внутри него нужно проверить, пуста очередь или нет:
void consumer() { while (true) { int var; { using namespace std::literals; std::unique_lock<std::mutex> lck { mtx }; bool res = cond.wait_for(lck, 10s, [] { return !queue.empty(); }); // <= if (!res) { break; } // no spurious wakeup var = queue.front(); queue.pop(); } do_smth(var); } }
Данная диагностика классифицируется как:
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V1089. |