C++ 线程间同步的条件变量 std::condition_variable 和 std::condition_variable_any

C++ 线程间同步的条件变量 std::condition_variable 和 std::condition_variable_any

flyfish

在C++中,std::condition_variablestd::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;
}
  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西笑生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值