Difference between revisions of "cpp/thread/condition variable"
Bogdanbebic (Talk | contribs) m (Fixed typo in synchronisation) |
(Fixing unsafe notification) |
||
Line 156: | Line 156: | ||
void close() | void close() | ||
{ | { | ||
− | done = true; | + | // It is not necessary to hold the lock while notifying, |
− | + | // but the lock must be held when updating the data. | |
+ | { | ||
+ | std::lock_guard lock{m}; | ||
+ | done = true; | ||
+ | notified = true; | ||
+ | } | ||
cond_var.notify_one(); | cond_var.notify_one(); | ||
} | } |
Revision as of 10:29, 8 April 2020
Defined in header <condition_variable>
|
||
class condition_variable; |
(since C++11) | |
The condition_variable
class is a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until another thread both modifies a shared variable (the condition), and notifies the condition_variable
.
The thread that intends to modify the variable has to
- acquire a
std::mutex
(typically via std::lock_guard) - perform the modification while the lock is held
- execute notify_one or notify_all on the
std::condition_variable
(the lock does not need to be held for notification)
Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.
Any thread that intends to wait on std::condition_variable
has to
- acquire a std::unique_lock<std::mutex>, on the same mutex as used to protect the shared variable
- execute wait, wait_for, or wait_until. The wait operations atomically release the mutex and suspend the execution of the thread.
- When the condition variable is notified, a timeout expires, or a spurious wakeup occurs, the thread is awakened, and the mutex is atomically reacquired. The thread should then check the condition and resume waiting if the wake up was spurious.
std::condition_variable
works only with std::unique_lock<std::mutex>; this restriction allows for maximal efficiency on some platforms. std::condition_variable_any provides a condition variable that works with any BasicLockable object, such as std::shared_lock.
Condition variables permit concurrent invocation of the wait, wait_for, wait_until, notify_one and notify_all member functions.
The class std::condition_variable
is a StandardLayoutType. It is not CopyConstructible, MoveConstructible, CopyAssignable, or MoveAssignable.
Contents |
Member types
Member type | Definition |
native_handle_type
|
implementation-defined |
Member functions
constructs the object (public member function) | |
destructs the object (public member function) | |
operator= [deleted] |
not copy-assignable (public member function) |
Notification | |
notifies one waiting thread (public member function) | |
notifies all waiting threads (public member function) | |
Waiting | |
blocks the current thread until the condition variable is awakened (public member function) | |
blocks the current thread until the condition variable is awakened or after the specified timeout duration (public member function) | |
blocks the current thread until the condition variable is awakened or until specified time point has been reached (public member function) | |
Native handle | |
returns the native handle (public member function) |
Example
condition_variable
is used in combination with a std::mutex to facilitate inter-thread communication.
#include <iostream> #include <string> #include <thread> #include <mutex> #include <condition_variable> std::mutex m; std::condition_variable cv; std::string data; bool ready = false; bool processed = false; void worker_thread() { // Wait until main() sends data std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return ready;}); // after the wait, we own the lock. std::cout << "Worker thread is processing data\n"; data += " after processing"; // Send data back to main() processed = true; std::cout << "Worker thread signals data processing completed\n"; // Manual unlocking is done before notifying, to avoid waking up // the waiting thread only to block again (see notify_one for details) lk.unlock(); cv.notify_one(); } int main() { std::thread worker(worker_thread); data = "Example data"; // send data to the worker thread { std::lock_guard<std::mutex> lk(m); ready = true; std::cout << "main() signals data ready for processing\n"; } cv.notify_one(); // wait for the worker { std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{return processed;}); } std::cout << "Back in main(), data = " << data << '\n'; worker.join(); }
Output:
main() signals data ready for processing Worker thread is processing data Worker thread signals data processing completed Back in main(), data = Example data after processing
synchronisation primitives used to send data from one thread to another grouped in a class
#include <condition_variable> #include <mutex> #include <thread> #include <iostream> #include <queue> #include <chrono> class condvarQueue { std::queue<int> produced_nums; std::mutex m; std::condition_variable cond_var; bool done = false; bool notified = false; public: void push(int i) { std::unique_lock<std::mutex> lock(m); produced_nums.push(i); notified = true; cond_var.notify_one(); } template<typename Consumer> void consume(Consumer consumer) { std::unique_lock<std::mutex> lock(m); while (!done) { while (!notified) { // loop to avoid spurious wakeups cond_var.wait(lock); } while (!produced_nums.empty()) { consumer(produced_nums.front()); produced_nums.pop(); } notified = false; } } void close() { // It is not necessary to hold the lock while notifying, // but the lock must be held when updating the data. { std::lock_guard lock{m}; done = true; notified = true; } cond_var.notify_one(); } }; int main() { condvarQueue queue; std::thread producer([&]() { for (int i = 0; i < 5; ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "producing " << i << '\n'; queue.push(i); } queue.close(); }); std::thread consumer([&]() { queue.consume([](int input){ std::cout << "consuming " << input << '\n'; }); }); producer.join(); consumer.join(); }
Output:
producing 0 consuming 0 producing 1 consuming 1 producing 2 consuming 2 producing 3 consuming 3 producing 4 consuming 4