This diagnostic rule is based on the CP.42 CppCoreGuidelines.
The analyzer has detected one of the non-static member functions of the 'std::condition_variable' class template — 'wait', 'wait_for' or 'wait_until' — is called without a predicate. This can lead to a spurious wakeup of a thread or a thread hanging.
Let's consider the example N1 leading to a potential hanging:
#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(); }
The example contains a race condition. The program can hang if it runs in the following order:
To fix this, we should modify the code as follows:
Here is the fixed example:
#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(); }
Let's consider the example N2 where a spurious wakeup can happen:
#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(); }
A spurious wakeup happens when a waiting thread wakes up and discovers that the condition it was expecting has not been met. This can occur in two scenarios:
In the example N2, a spurious wakeup can occur in threads 'c1', 'c2', and 'c3'. As a result of such a wakeup, the queue may be empty — accessing it, we can get an undefined behavior.
To fix this, we should also call the 'std::condition_variable::wait_for' overload that accepts the predicate. Inside the predicate, we need to check whether the queue is empty or not:
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); } }
This diagnostic is classified as:
You can look at examples of errors detected by the V1089 diagnostic. |