Bootstrap

C++并行处理支持库 之四

条件变量

概述

条件变量是一个同步原语,允许多个线程相互通信。它允许一定数量的线程等待(可能超时)来自另一个线程的通知,它们可以继续。条件变量始终与互斥锁相关联。

std::condition_variable 是一种与 std::mutex 一起使用的同步原语,用于阻止一个或多个线程,直到另一个线程修改共享变量(条件),并通知 std::condition_variable。

打算修改共享变量的线程必须:

  • 获取 std::mutex(通常通过 std::lock_guard)。
  • 在拥有锁的情况下修改共享变量。
  • 对 std::condition_variable 调用notify_one或notify_all(可以在释放锁后完成)。

即使共享变量是原子的,为了正确地将修改发布到等待线程,也必须在拥有mutex时,对其进行修改。

任何想要等待 std::condition_variable 的线程必须:

  • 获取用于保护共享变量的互斥锁上的 std::unique_lockstd::mutex。
  • 执行以下操作之一:
  1. 检查情况,以防已更新,并通知。
  2. 对 std::condition_variable 调用 wait、wait_for 或 wait_until,以原子方式释放mutex,并挂起线程执行,直到通知条件变量、超时到期,或发生虚假唤醒,然后在返回之前,以原子方式获取mutex。
  3. 检查条件,如果不满足,则继续等待。

或者:

  1. 使用 wait、wait_for 和 wait_until 的谓词重载,它执行相同的三个步骤。

std::condition_variable 仅适用于 std::unique_lockstd::mutex,它允许在某些平台上实现最大效率。 std::condition_variable_any 提供可与任何 BasicLockable 对象一起使用的条件变量,例如 std::shared_lock。

条件变量允许同时调用 wait、wait_for、wait_until、notify_one 和 notify_all 成员函数。

std::condition_variable类是一个 StandardLayoutType。它不是 CopyConstructible, MoveConstructible, CopyAssignable, 或MoveAssignable。

构造器

condition_variable();
condition_variable( const condition_variable& ) = delete;

APIs

等待APIs

API说明
wait阻塞当前线程,直到条件变量被唤醒
wait_for阻塞当前线程,直到条件变量被唤醒,或指定的超时发生
wait_until阻塞当前线程,直到条件变量被唤醒,或到达指定时间点

唤醒APIs

API说明
notify_one通知一个等待线程
notify_all通知所有等待线程

编程实列

下列代码说明如何在程序中使用std::condition_variable。

#include  <iostream>
#include  <condition_variable>
#include  <string>
#include  <mutex>
#include  <thread>

using namespace std;

mutex m;
condition_variable cv;
string m_data;
bool ready = false;
bool processed = false;

void worker_thread()
{
    // wait until main() set variable "ready"
    unique_lock lk(m);
    cv.wait(lk, []{ return ready; });
 
    // after the wait, we own the lock
    cout << "Worker thread is processing data\n";
    m_data += " has been processed";
 
    // set variable "processed" to tell main worker thread done
    processed = true;
    cout << "Worker thread 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()
{
    thread worker(worker_thread);
 
    m_data = "Example data";
    // set variable "ready" to activate the worker thread
    {
        lock_guard lk(m);
        ready = true;
        cout << "data ready for processing\n";
    }
    cv.notify_one();
 
    // wait for the worker thread done
    {
        unique_lock lk(m);
        cv.wait(lk, []{ return processed; });
    }
    cout << "data = " << m_data << '\n';
 
    worker.join();
}
data ready for processing
Worker thread is processing data
Worker thread data processing completed
data = Example data has been processed
;