C++ Concurrency Guide std::condition_variable

The <condition_variable> header file mainly contains classes and functions related to condition variables. Related classes include std::condition_variable and std::condition_variable_any, as well as the enumeration type std::cv_status. In addition, it also includes the function std::notify_all_at_thread_exit(). The above types are introduced below.

Introduction to std::condition_variable

When a wait function of the std::condition_variable object is called, it uses std::unique_lock (via std::mutex) to lock the current thread. The current thread will be blocked until another thread calls the notification function on the same std::condition_variable object to wake up the current thread.

The std::condition_variable object usually uses std::unique_lock<std::mutex> to wait. If you need to use another lockable type, you can use the std::condition_variable_any class. The usage of std::condition_variable_any will be discussed later in this article.

Let's first look at a simple example:

#include <iostream>                 // std::cout
#include <thread>                   // std::thread
#include <mutex>                    // std::mutex, std::unique_lock
#include <condition_variable>       // std::condition_variable

std::mutex mtx;                     // 全局互斥锁.
std::condition_variable cv;         // 全局条件变量.
bool ready = false;                 // 全局标志位.

void do_print_id(int id)
{
    
    
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready)       // 如果标志位不为 true, 则等待...
        cv.wait(lck);    // 当前线程被阻塞, 当全局标志位变为 true 之后,

    // 线程被唤醒, 继续往下执行打印线程编号id.
    std::cout << "thread " << id << '\n';
}

void go()
{
    
    
    std::unique_lock <std::mutex> lck(mtx);
    ready = true;        // 设置全局标志位为 true.
    cv.notify_all();     // 唤醒所有线程.
}

int main()
{
    
    
    std::thread threads[10];  // spawn 10 threads:
    
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);

    std::cout << "10 threads ready to race...\n";
    go(); // go!

    for (auto & th:threads)
        th.join();

    return 0;
}

Output result:

10 threads ready to race...
thread 8
thread 2
thread 1
thread 4
thread 5
thread 7
thread 9
thread 0
thread 3
thread 6

Okay, now that we have a basic understanding of condition variables, let's take a look at the various member functions of std::condition_variable.

std::condition_variable constructor

default condition_variable();
copy [deleted] condition_variable (const condition_variable&) = delete;

The copy constructor of std::condition_variable is disabled and only the default constructor is provided.

std::condition_variable::wait() 介绍

unconditional void wait (unique_lock< mutex >& lck);
predicate template < class Predicate > void wait (unique_lock< mutex >& lck, Predicate pred);

std::condition_variable provides two wait() functions. After the current thread calls wait(), it will be blocked (at this time, the current thread should have acquired the lock (mutex), so let's set the lock lck) until another thread calls notify_* to wake up the current thread.

When a thread is blocked, this function will automatically call lck.unlock() to release the lock, so that other threads that are blocked on lock contention can continue to execute. In addition, once the current thread is notified (notified, usually another thread calls notify_* to wake up the current thread), the wait() function also automatically calls lck.lock(), making the status of lck the same as when the wait function is called.

In the second case (that is, Predicate is set), only when the pred condition is false, call wait() will block the current thread, and will be unblocked only when pred is true after receiving notification from other threads . So the second case is similar to the following code:

while (!pred()) wait(lck);

Consider the following example:

#include <iostream>                // std::cout
#include <thread>                  // std::thread, std::this_thread::yield
#include <mutex>                   // std::mutex, std::unique_lock
#include <condition_variable>      // std::condition_variable

std::mutex mtx;
std::condition_variable cv;

int cargo = 0;

bool shipment_available()
{
    
    
    return cargo != 0;
}

// 消费者线程.
void consume(int n)
{
    
    
    for (int i = 0; i < n; ++i) {
    
    
        std::unique_lock <std::mutex> lck(mtx);
        cv.wait(lck, shipment_available);
        std::cout << cargo << '\n';
        cargo = 0;
    }
}

int main()
{
    
    
    std::thread consumer_thread(consume, 10); // 消费者线程.

    // 主线程为生产者线程, 生产 10 个物品.
    for (int i = 0; i < 10; ++i) {
    
    
        while (shipment_available())
            std::this_thread::yield();
            
        std::unique_lock <std::mutex> lck(mtx);
        cargo = i + 1;
        cv.notify_one();
    }

    consumer_thread.join();

    return 0;
}

std::condition_variable::wait_for() 介绍

Insert picture description here
Similar to std::condition_variable::wait(), but wait_for can specify a time period. The thread will be blocked until the current thread receives notification or the specified time rel_time expires. Once it times out or receives notification from other threads, wait_for returns, and the remaining processing steps are similar to wait().

In addition, the last parameter pred of the overloaded version of wait_for (predicte(2)) represents the prediction condition of wait_for. Only when the pred condition is false, wait() will be called to block the current thread, and after receiving notifications from other threads Only when pred is true will it be unblocked, so it is equivalent to the following code:

return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));

Please see the following example. In the following example, the main thread waits for the th thread to input a value, and then prints out the value received by the th thread from the terminal. Before the th thread receives the value, the main thread waits for a one-second timeout. Once, and print a ".":

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>             // std::chrono::seconds
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status

std::condition_variable cv;

int value;

void do_read_value()
{
    
    
    std::cin >> value;
    cv.notify_one();
}

int main ()
{
    
    
    std::cout << "Please, enter an integer (I'll be printing dots): \n";
    std::thread th(do_read_value);

    std::mutex mtx;
    std::unique_lock<std::mutex> lck(mtx);
    
    while (cv.wait_for(lck,std::chrono::seconds(1)) == std::cv_status::timeout) {
    
    
        std::cout << '.';
        std::cout.flush();
    }

    std::cout << "You entered: " << value << '\n';

    th.join();
    
    return 0;
}

std::condition_variable::wait_until 介绍

Insert picture description here
Similar to std::condition_variable::wait_for, but wait_until can specify a time point. The thread will be blocked until the current thread receives notification or the specified time point abs_time expires. Once it times out or receives notification from other threads, wait_until returns, and the remaining processing steps are similar to wait_until().

In addition, the last parameter pred of the overloaded version of wait_until (predicte(2)) represents the prediction condition of wait_until. Only when the pred condition is false, wait() will block the current thread, and after receiving notifications from other threads Only when pred is true will it be unblocked, so it is equivalent to the following code:

while (!pred())
  if ( wait_until(lck,abs_time) == cv_status::timeout)
    return pred();
return true;

std::condition_variable::notify_one() 介绍

Wake up a waiting (wait) thread. If there is no waiting thread, the function does nothing. If there are multiple waiting threads at the same time, it is unspecified to wake up a certain thread.

#include <iostream>                // std::cout
#include <thread>                  // std::thread
#include <mutex>                   // std::mutex, std::unique_lock
#include <condition_variable>      // std::condition_variable

std::mutex mtx;
std::condition_variable cv;

int cargo = 0; // shared value by producers and consumers

void consumer()
{
    
    
    std::unique_lock < std::mutex > lck(mtx);
    while (cargo == 0)
        cv.wait(lck);
    std::cout << cargo << '\n';
    cargo = 0;
}

void producer(int id)
{
    
    
    std::unique_lock < std::mutex > lck(mtx);
    cargo = id;
    cv.notify_one();
}

int main()
{
    
    
    std::thread consumers[10], producers[10];

    // spawn 10 consumers and 10 producers:
    for (int i = 0; i < 10; ++i) {
    
    
        consumers[i] = std::thread(consumer);
        producers[i] = std::thread(producer, i + 1);
    }

    // join them back:
    for (int i = 0; i < 10; ++i) {
    
    
        producers[i].join();
        consumers[i].join();
    }

    return 0;
}

std::condition_variable::notify_all() 介绍

Wake up all waiting (wait) threads. If there is no waiting thread, this function does nothing.
Consider the following example:

#include <iostream>                // std::cout
#include <thread>                  // std::thread
#include <mutex>                   // std::mutex, std::unique_lock
#include <condition_variable>      // std::condition_variable

std::mutex mtx;                    // 全局互斥锁.
std::condition_variable cv;        // 全局条件变量.
bool ready = false;                // 全局标志位.

void do_print_id(int id)
{
    
    
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready)      // 如果标志位不为 true, 则等待...
        cv.wait(lck);   // 当前线程被阻塞, 当全局标志位变为 true 之后,
    // 线程被唤醒, 继续往下执行打印线程编号id.
    std::cout << "thread " << id << '\n';
}

void go()
{
    
    
    std::unique_lock <std::mutex> lck(mtx);
    ready = true;      // 设置全局标志位为 true.
    cv.notify_all();   // 唤醒所有线程.
}

int main()
{
    
    
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);

    std::cout << "10 threads ready to race...\n";
    go(); // go!

    for (auto & th:threads)
        th.join();

    return 0;
}

Introduction to std::condition_variable_any

Similar to std::condition_variable, except that the wait function of std::condition_variable_any can accept any lockable parameter, while std::condition_variable can only accept std::unique_lockstd::mutex type parameters, in addition to std::condition_variable It's almost exactly the same.

Introduction to std::cv_status enumeration type

Insert picture description here

std::notify_all_at_thread_exit

The function prototype is:

void notify_all_at_thread_exit (condition_variable& cond, unique_lock<mutex> lck);

When the thread that called the function exits, all threads waiting on the cond condition variable will be notified.
Consider the following example:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) {
    
    
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);
  // ...
  std::cout << "thread " << id << '\n';
}

void go() {
    
    
  std::unique_lock<std::mutex> lck(mtx);
  std::notify_all_at_thread_exit(cv,std::move(lck));
  ready = true;
}

int main ()
{
    
    
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_id,i);
    
  std::cout << "10 threads ready to race...\n";

  std::thread(go).detach();   // go!

  for (auto& th : threads) th.join();

  return 0;
}

Guess you like

Origin blog.csdn.net/qq_24649627/article/details/114322040