C++ 线程间同步的条件变量 std::condition_variable 和 std::condition_variable_any
flyfish
在C++中,std::condition_variable
和 std::condition_variable_any
是两种用于线程间同步的条件变量。它们主要用于让一个线程等待某个条件成立,另一个线程通知条件变化。
std::condition_variable
修改共享变量的线程:
必须通过 std::lock_guard 获取互斥锁。
在持有锁的情况下修改共享数据。
调用 notify_one 或 notify_all 通知 std::condition_variable(可以在释放锁之后进行)。
等待线程:
必须使用 std::unique_lock<std::mutex>
获取互斥锁。
可以选择以下几种方式之一:
检查条件是否已经被更新和通知。
调用 wait、wait_for 或 wait_until 方法(自动释放互斥锁并挂起线程,直到条件变量被通知、超时到期或发生虚假唤醒,然后重新获取互斥锁并返回)。
检查条件并继续等待,如果条件尚未满足。
使用带有谓词的重载版本的 wait、wait_for 或 wait_until 方法,这些方法会自动执行上述步骤。
谓词解释
支持谓词:线程会等待直到谓词返回 true,这意味着线程会在条件满足时自动继续执行。
不支持谓词:线程在被唤醒后必须手动检查条件是否满足,如果条件不满足,线程可能需要再次进入等待状态
函数解释:
wait_for: 使线程等待指定的时间,直到收到通知或超时。
wait_until: 使线程等待直到某个时间点,直到收到通知或时间到达。
std::condition_variable 的示例代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
// 全局变量声明
std::mutex mtx; // 互斥锁,用于保护共享数据
std::condition_variable cv; // 条件变量,用于线程间通信
bool ready = false; // 条件标志,用于控制线程行为
// 线程函数,等待直到 ready 为 true 或超时
void wait_thread() {
std::unique_lock<std::mutex> lck(mtx); // 使用互斥锁保护共享数据
while (!ready) { // 当条件不满足时,循环等待
std::cout << "Waiting...\n"; // 输出等待信息
// 等待条件变量的通知或者超时1秒钟
if (cv.wait_for(lck, std::chrono::seconds(1)) == std::cv_status::timeout) {
std::cout << "Timeout\n"; // 输出超时信息
} else {
std::cout << "Notified\n"; // 输出收到通知信息
}
}
std::cout << "Thread continues execution\n"; // 条件满足,线程继续执行
}
// 线程函数,通知条件改变
void notify_thread() {
std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟一些工作,延迟5秒
{
std::lock_guard<std::mutex> lck(mtx); // 使用互斥锁保护共享数据
ready = true; // 改变条件标志
}
cv.notify_one(); // 通知一个等待中的线程
}
int main() {
std::thread t1(wait_thread); // 创建等待线程
std::thread t2(notify_thread); // 创建通知线程
t1.join(); // 等待等待线程结束
t2.join(); // 等待通知线程结束
return 0;
}
输出
Waiting...
Timeout
Waiting...
Timeout
Waiting...
Timeout
Waiting...
Timeout
Waiting...
Notified
Thread continues execution
std::condition_variable_any 的示例代码
与仅限于与 std::unique_lock<std::mutex>
一起使用的 std::condition_variable 不同,
std::condition_variable_any 可以与任何实现了 BasicLockable 接口的锁一起使用,这意味着它不仅限于 std::unique_lock<std::mutex>
,还可以与 std::shared_lock、std::recursive_mutex、std::timed_mutex 等一起使用。
BasicLockable
为了使一个类型 L 满足 BasicLockable 要求,对于该类型的对象 m,以下条件必须得到满足:
m.lock() 方法:
效果:阻塞当前执行代理(通常是线程),直到可以获取锁。如果抛出异常,则不会获取锁。
m.unlock() 方法:
前置条件:当前执行代理持有 m 的非共享锁。
效果:释放由当前执行代理持有的非共享锁。
异常保证:不抛出异常。
解释
lock() 方法:
当调用 lock() 时,如果锁当前不可用(即已被另一个执行代理持有),那么当前执行代理将被阻塞,直到锁变得可用。如果在尝试获取锁的过程中抛出了异常,那么锁不会被获取。
unlock() 方法:
当调用 unlock() 时,必须保证当前执行代理持有该锁。调用 unlock() 会释放锁,并且不会抛出任何异常
在 C++ 的并发模型中,“执行代理”可以指代以下几种实体:
线程 (std::thread):
一个独立执行的程序路径,每个线程都可以执行不同的任务或同一任务的不同部分。
线程是最常见的执行代理形式。
进程 (std::process):
虽然 C++ 标准库并不直接支持进程,但可以通过外部库或操作系统 API 来创建和管理进程。
进程是独立的地址空间,通常包含一个或多个线程。
任务 (std::future 和 std::packaged_task):
任务是一个异步执行的单元,通常用于表达式计算或数据处理。
任务可以在线程池或其他调度器中执行。
协程 (std::coroutine 和 std::suspend):
协程是一种特殊的执行单元,可以在程序的不同位置暂停和恢复执行。
在 C++20 中引入了协程支持,它们可以用于实现异步和非阻塞编程。
std::condition_variable_any
可以与多种锁类型一起使用,包括 std::unique_lock<std::mutex>
、std::shared_lock<std::shared_mutex>
和 std::unique_lock<std::timed_mutex>
。这样在编写并发代码时,可以根据具体的需求选择合适的锁类型来搭配使用 std::condition_variable_any
,下面展示例子。
示例 1:使用 std::unique_lock<std::mutex>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
std::mutex mtx; // 互斥锁
std::condition_variable_any cv_any; // 通用条件变量
bool ready = false; // 条件变量标志
void print_id(int id) {
std::unique_lock<std::mutex> lck(mtx); // 使用 unique_lock 锁住互斥锁
while (!ready) {
cv_any.wait(lck); // 等待条件变量,直到 ready 为 true
}
// 输出线程ID
std::cout << "Thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lck(mtx); // 使用 unique_lock 锁住互斥锁
ready = true;
cv_any.notify_all(); // 通知所有等待的线程
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(print_id, i); // 创建并启动线程
}
std::this_thread::sleep_for(std::chrono::seconds(1)); // 主线程等待1秒
go(); // 设置 ready 为 true 并通知所有线程
for (auto& th : threads) {
th.join(); // 等待所有线程完成
}
return 0;
}
示例 2:使用 std::shared_lock<std::shared_mutex>
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <condition_variable>
#include <vector>
std::shared_mutex shared_mtx; // 共享互斥锁
std::condition_variable_any cv_any; // 通用条件变量
bool ready = false; // 条件变量标志
void print_id(int id) {
std::shared_lock<std::shared_mutex> lck(shared_mtx); // 使用 shared_lock 锁住共享互斥锁
while (!ready) {
cv_any.wait(lck); // 等待条件变量,直到 ready 为 true
}
// 输出线程ID
std::cout << "Thread " << id << '\n';
}
void go() {
std::unique_lock<std::shared_mutex> lck(shared_mtx); // 使用 unique_lock 锁住共享互斥锁
ready = true;
cv_any.notify_all(); // 通知所有等待的线程
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(print_id, i); // 创建并启动线程
}
std::this_thread::sleep_for(std::chrono::seconds(1)); // 主线程等待1秒
go(); // 设置 ready 为 true 并通知所有线程
for (auto& th : threads) {
th.join(); // 等待所有线程完成
}
return 0;
}
示例 3:使用 std::unique_lock<std::timed_mutex>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
std::timed_mutex tmtx; // 定时互斥锁
std::condition_variable_any cv_any; // 通用条件变量
bool ready = false; // 条件变量标志
void print_id(int id) {
std::unique_lock<std::timed_mutex> lck(tmtx); // 使用 unique_lock 锁住定时互斥锁
while (!ready) {
cv_any.wait(lck); // 等待条件变量,直到 ready 为 true
}
// 输出线程ID
std::cout << "Thread " << id << '\n';
}
void go() {
std::unique_lock<std::timed_mutex> lck(tmtx); // 使用 unique_lock 锁住定时互斥锁
ready = true;
cv_any.notify_all(); // 通知所有等待的线程
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(print_id, i); // 创建并启动线程
}
std::this_thread::sleep_for(std::chrono::seconds(1)); // 主线程等待1秒
go(); // 设置 ready 为 true 并通知所有线程
for (auto& th : threads) {
th.join(); // 等待所有线程完成
}
return 0;
}
最后一个
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <shared_mutex>
// 自定义 BasicLockable 类型
class CustomLock : public std::enable_shared_from_this<CustomLock> {
public:
void lock() {
m_mutex.lock();
}
void unlock() noexcept {
m_mutex.unlock();
}
private:
std::mutex m_mutex;
};
// 使用 std::shared_mutex 和 std::shared_lock
void wait_with_shared_mutex(std::shared_mutex& sm, std::condition_variable_any& cv_any, bool& ready) {
std::shared_lock<std::shared_mutex> lock(sm);
while (!ready) {
cv_any.wait(lock);
}
std::cout << "Thread was notified and now is executing." << std::endl;
}
// 使用自定义 BasicLockable 类型
void wait_with_custom_lock(CustomLock& cl, std::condition_variable_any& cv_any, bool& ready) {
std::unique_lock<CustomLock> lock(cl); // 使用 unique_lock 锁住自定义锁
while (!ready) {
cv_any.wait(lock);
}
std::cout << "Thread was notified and now is executing with custom lock." << std::endl;
}
void set_ready(std::shared_mutex& sm, std::condition_variable_any& cv_any, bool& ready) {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::unique_lock<std::shared_mutex> lk(sm); // 使用 unique_lock 锁住共享互斥锁
ready = true;
}
cv_any.notify_all();
}
int main() {
std::shared_mutex shared_mutex;
std::condition_variable_any condition_variable_any;
bool ready = false;
CustomLock custom_lock;
std::thread t1(wait_with_shared_mutex, std::ref(shared_mutex), std::ref(condition_variable_any), std::ref(ready));
std::thread t2(wait_with_custom_lock, std::ref(custom_lock), std::ref(condition_variable_any), std::ref(ready));
std::thread t3(set_ready, std::ref(shared_mutex), std::ref(condition_variable_any), std::ref(ready));
t1.join();
t2.join();
t3.join();
return 0;
}