Is this code robust
There are a few race conditions and deadlocking issues. The main one being that shouldStart may be in a waiting state when the second thread exits which will cause it to sit and wait forever. In the simple case of this code that can be solved by adding a call to SignalStart after exiting the loop in the second thread, if the first thread is actually doing work you'd need to add some checks to see if it should exit early. Others involve data read/write races which can be fixed by adding more locking.
Condition variables have a problem called spurious wakeup. This means the wait() function may return before the cv has been notified. To fix this, the wait() function always needs to be done in a loop or by using the other overload which includes a predicate to check if the cv should actually have woken up.
The lock.unlock()s you have aren't necessary as the mutex will be unlocked automatically when the lock is destroyed. (you may want to keep them if you're doing work after waiting for the cv and the lock is no longer needed)
Here's some updated code that I believe won't have any issues. It may being doing extra locking that isn't necessary but that's better than deadlocks. Lines with //f were added or changed.
#include <iostream> #include <thread> #include <mutex> class ThreadController { mutable std::mutex mtx;//f std::condition_variable shouldStart; std::condition_variable hasEnded; bool isRunning; bool shouldExit; public: void SignalStart() { std::unique_lock<std::mutex> lock(mtx);//f isRunning = true; shouldStart.notify_one(); } void WaitForStart() { std::unique_lock<std::mutex> lock(mtx);//f while (!isRunning && !shouldExit) {//f shouldStart.wait(lock); } } void SignalEnd() { std::unique_lock<std::mutex> lock(mtx);//f isRunning = false; hasEnded.notify_one(); } void WaitForEnd() { std::unique_lock<std::mutex> lock(mtx);//f while (isRunning && !shouldExit) {//f hasEnded.wait(lock); } } void SignalExit() { std::unique_lock<std::mutex> lock(mtx);//f shouldExit = true; } bool ShouldExit() const { std::unique_lock<std::mutex> lock(mtx);//f return shouldExit; } }; void func1(ThreadController * threadController) { do { threadController->WaitForStart(); std::cout << "started" << std::endl; threadController->SignalEnd(); } while (!threadController->ShouldExit()); } void func2(ThreadController * threadController) { int i = 0; do { threadController->SignalStart(); ++i; //Obviously the following isn't actually what would be here if (i > 100) { threadController->SignalExit(); } threadController->WaitForEnd(); std::cout << "ended" << std::endl; } while (!threadController->ShouldExit()); threadController->SignalStart(); //f } int main() { ThreadController * tc = new ThreadController(); std::thread t1(func1, tc); std::thread t2(func2, tc); t1.join(); t2.join(); delete tc; }
Edit: Those unique_locks that aren't being used for condition_variables should be lock_guards, they're slightly faster if you don't need unlocking and relocking capabilities.
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now