std::condition_variable condition variable and lock_guard, unique_lock

Used to block one thread, or block multiple threads at the same time, until another thread modifies the shared variable (condition) and notifies condition_variable.

A thread that intentionally modifies a variable must

  1. Obtain std::mutex (often via std::lock_guard )
  2. Modify while holding lock
  3. Perform notify_one or notify_all on std::condition_variable (no need to hold locks for notifications)

Even if a shared variable is atomic, it must be modified under mutual exclusion.

Any thread that intentionally waits on std::condition_variable must

  1. Get a std::unique_lock on the same mutex used to protect the shared variable
  2. Do one of the following:
  1. Check if the condition is updated or reminded of it
  2. Execute wait, wait_for or wait_until, the waiting operation automatically releases the mutex and suspends the execution of the thread.
  3. When condition_variable is notified, the time limit expires or a false wake-up occurs, the thread is woken up and the mutex is automatically regained. The thread should then check the condition and continue waiting if the wakeup is false.

1: Commonly used member functions

Notification

notify_one

Notify a waiting thread (public member function), randomly notify a thread when there are multiple threads

notify_all

Notify all waiting threads (public member function)
Waiting

wait

Block the current thread until the condition variable is awakened (public member function)

wait_for

Block the current thread until the condition variable is awakened, or until the specified time limit expires (public member function)

wait_until

Block the current thread until the condition variable is awakened, or until the specified time point is reached (public member function)

wait:

void wait( std::unique_lock<std::mutex> & amp; lock );
template< class Predicate >
void wait( std::unique_lock<std::mutex> & amp; lock, Predicate pred );

Contains two overloads, the first one only contains unique_lock objects, and the other is a Predicate object (wait condition). Unique_lock must be used here because of the working principle of the wait function:

  • The current thread will be blocked after calling wait() and the function will unlock the mutex until another thread calls notify_one or notify_all to wake up the current thread; once the current thread is notified (notify), wait() The function also automatically callslock(), and similarly cannotuselock_guard< strong>Object.
  • Ifwaitdoes not have a second parameter, the default condition is not met for the first call, the mutex is unlocked directly and blocked in this line until a certain Until a thread calls notify_one or notify_all, after being awakened, wait retries to obtain the mutex. If it cannot be obtained, the thread will be stuck here until the mutex is obtained, and then continue to perform subsequent operations unconditionally.
  • Ifwaitcontains the second parameter, if the second parameter is not satisfied, then wait will unlock the mutex and block to this line, Until a thread calls notify_one or notify_all, after being awakened, wait will try to obtain the mutex again. If it cannot be obtained, the thread will be stuck here until the mutex is obtained,and then continue to judge the second parameter. , if the expression is false, wait unlocks the mutex and blocks the current line until a thread calls notify_one or notify_all. If it is true, perform the following operations. .
//2) Equivalent to
while (!pred()) {
    wait(lock);
}

wait_for:

template< class Rep, class Period >
std::cv_status wait_for( std::unique_lock<std::mutex> & amp; lock,
                         const std::chrono::duration<Rep, Period> & amp; rel_time);

template< class Rep, class Period, class Predicate >
bool wait_for( std::unique_lock<std::mutex> & amp; lock,
               const std::chrono::duration<Rep, Period> & rel_time,
               Predicate pred);

and wait
the difference is,
wait_for
It can be executed for a period of time. Before the thread receives the wake-up notification or the time times out, the thread will be in a blocked state. If the thread receives the wake-up notification or the time times out, wait_for
Return, remaining operations and
wait
similar.

wait_unti:

template< class Clock, class Duration >
std::cv_status
    wait_until( std::unique_lock<std::mutex> & amp; lock,
                const std::chrono::time_point<Clock, Duration> & amp; timeout_time );

template< class Clock, class Duration, class Pred >
bool wait_until( std::unique_lock<std::mutex> & amp; lock,
                 const std::chrono::time_point<Clock, Duration> & amp; timeout_time,
                 Pred pred );

with wait_for
Similar, just
wait_until
You can specify a time point, and the thread will be blocked until the current thread receives the notification or times out at the specified time point. If it times out or receives a wake-up notification, wait_until
Return, remaining operations and
wait
similar.

notity_one:

wake up
One of the threads waiting for the current condition. If no thread is waiting, the function does nothing. If it is waiting
If there is more than one thread, the thread to be awakened is undefined.

notiy_all:


Wakes up all threads that are waiting for the current condition. If there are no waiting threads, the function does nothing.

2: The use and difference between lock_guard and unique_lock

Occupying the mutex during the scope block provides convenience RAII style mechanism. When the lock_guard/unique_lock object is created, the mutex will be locked; when leaving the lock_guard/unique_lock scope, the lock_guard/unique_lock will be destroyed and the mutex will be released.

#include <thread>
#include <mutex>
#include <iostream>
 
int g_i = 0;
std::mutex g_i_mutex; // protect g_i
 
void safe_increment()
{
    std::lock_guard<std::mutex> lock(g_i_mutex);
     + + g_i;
 
    std::cout << std::this_thread::get_id() << ": " << g_i << '\\
';
 
    // g_i_mutex is automatically released when the lock leaves scope
}
 
int main()
{
    std::cout << "main: " << g_i << '\\
';
 
    std::thread t1(safe_increment);
    std::thread t2(safe_increment);
 
    t1.join();
    t2.join();
 
    std::cout << "main: " << g_i << '\\
';
}

The difference between unique_lock and lock_guard:

  • Both unique_lock and lock_guard can realize automatic locking and unlocking, but the former is more flexible and can implement more functions.
  • unique_lock
    Can be temporarily unlocked and relocked, such as after constructing the object
    lck.unlock()
    You can unlock it and lock it with lck.lock() without having to wait until it is automatically unlocked during destruction.

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Algorithm skill tree Home page Overview 57526 people are learning the system