多线程同步利器:条件变量 Condition Variable 的深度解析

在这里插入图片描述

😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的微信交流:sssun902
🎈 本文专栏:本文收录于《C++11》系列专栏,相信一份耕耘一份收获,我会分享相关学习内容,不说废话,祝大家都offer拿到手软
🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🖥随时欢迎您跟我沟通,一起交流,一起成长、进步!

多线程同步利器:条件变量 Condition Variable 的深度解析

引言

在并发编程中,条件变量是用于线程间同步的一种非常强大的工具。它允许线程在某些条件成立之前挂起,直到其他线程改变条件并发出通知。条件变量通常与互斥锁(mutex)一起使用,以保护共享数据并避免竞争条件。本文将详细分析条件变量的用法,包括其参数解析和实际应用示例。
在这里插入图片描述

1. 条件变量的基本概念

条件变量用于线程同步,使得线程能够在某些条件不满足时挂起,并在条件被满足时得到通知并继续执行。条件变量的主要用途包括:

  • 同步线程间的工作进度。
  • 等待特定的事件或状态变化。
  • 避免不必要的CPU循环,提高效率。

2. 条件变量的参数解析

在 C++11 及以后的版本中,条件_variable 的相关函数通常接受以下参数:

  • std::mutex& mut:与条件变量关联的互斥锁,用于保护条件变量和共享数据。
  • std::unique_lock<std::mutex>& lock:在等待条件变量之前,线程应持有互斥锁的 unique_lock。在等待时,unique_lock 将被自动释放互斥锁,允许其他线程进入临界区并可能改变条件。
  • Predicate pred(可选):谓词函数,用于在唤醒后再次检查条件是否满足。

3. 条件变量的工作流程

  1. 锁定互斥锁:线程首先需要锁定一个互斥锁。
  2. 检查条件:在互斥锁的保护下检查条件是否满足。
  3. 挂起等待:如果不满足,线程将通过 std::condition_variable::wait 或相关函数挂起,并释放互斥锁。
  4. 唤醒线程:当其他线程改变条件并调用 notify_onenotify_all 时,等待的线程将被唤醒。
  5. 重新锁定互斥锁:被唤醒的线程将重新获得互斥锁,并再次检查条件。

4. 示例代码

以下是一个使用条件变量的示例,包括参数的使用:

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

std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
bool ready = false;

void worker_thread() {
    // 线程1:等待数据准备完成
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck, []{return ready;}); // 带参数的等待,再次检查条件
    std::cout << "Worker thread is processing data." << std::endl;
    // ... 处理数据 ...
}

void main_thread() {
    // 准备数据 ...
    {
        std::lock_guard<std::mutex> lck(mtx);
        ready = true;
    } // lock_guard 析构时自动释放锁
    cv.notify_one(); // 通知一个等待的线程

    std::thread worker(worker_thread);
    worker.join();
}

int main() {
    main_thread();
    return 0;
}

5. 注意事项深入分析

在使用条件变量时,有几个关键点需要注意,以确保线程同步的正确性和效率。

避免虚假唤醒

虚假唤醒是指线程可能在没有收到通知的情况下被唤醒。这种情况虽然不常见,但确实会发生。因此,使用条件变量时,必须在每次唤醒后重新检查条件是否满足。这通常通过在 wait 调用中使用一个谓词来实现。

示例代码

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

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

void worker_thread() {
    std::unique_lock<std::mutex> lck(mtx);
    // 使用谓词确保条件满足
    cv.wait(lck, []{return ready;});
    std::cout << "Worker thread is processing data." << std::endl;
    // ... 处理数据 ...
}

void main_thread() {
    {
        std::lock_guard<std::mutex> lck(mtx);
        ready = true;
    } // lock_guard 析构时自动释放锁
    cv.notify_one(); // 通知一个等待的线程

    std::thread worker(worker_thread);
    worker.join();
}

int main() {
    main_thread();
    return 0;
}

在这个示例中,cv.wait 调用中的谓词 []{return ready;} 确保即使发生虚假唤醒,线程也只在 readytrue 时才退出等待状态。

使用 std::unique_lock

std::unique_lock 是使用条件变量时推荐的方式,因为它在等待条件变量时可以释放互斥锁,并在唤醒后重新获得互斥锁。这允许其他线程在等待线程被唤醒之前进入临界区并修改共享数据。

示例代码

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

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

void worker_thread() {
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck, []{return done;});
    std::cout << "Worker thread is processing after being notified." << std::endl;
    // ... 处理数据 ...
}

void notifier_thread() {
    {
        std::lock_guard<std::mutex> lck(mtx);
        done = true;
    } // lock_guard 析构时自动释放锁
    cv.notify_one(); // 通知等待的线程
}

int main() {
    std::thread worker(worker_thread);
    std::thread notifier(notifier_thread);

    worker.join();
    notifier.join();

    return 0;
}

在这个示例中,worker_thread 使用 unique_lock 来等待条件变量,而 notifier_thread 在设置 donetrue 后通知等待的线程。

锁的所有权

在使用条件变量时,必须确保在调用等待函数之前拥有互斥锁的所有权,并在等待结束后正确地重新获得和释放锁。这通常通过使用 std::unique_lock 来自动管理。

6. 条件变量的变体:std::condition_variable_any

std::condition_variable_anystd::condition_variable 的一个变体,它可以与任何类型的互斥锁一起使用,包括 std::recursive_mutexstd::timed_mutex。这为多线程同步提供了更大的灵活性。

示例代码

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

std::recursive_mutex mtx;
std::condition_variable_any cv;
bool ready = false;

void worker_thread() {
    std::unique_lock<std::recursive_mutex> lck(mtx);
    cv.wait(lck, []{return ready;});
    std::cout << "Worker thread is processing data." << std::endl;
    // ... 处理数据 ...
}

void main_thread() {
    {
        std::lock_guard<std::recursive_mutex> lck(mtx);
        ready = true;
    } // lock_guard 析构时自动释放锁
    cv.notify_one(); // 通知一个等待的线程

    std::thread worker(worker_thread);
    worker.join();
}

int main() {
    main_thread();
    return 0;
}

在这个示例中,std::recursive_mutex 被用作互斥锁,与 std::condition_variable_any 一起使用,展示了如何使用不同类型的互斥锁与条件变量。

结语

条件变量是多线程同步中一个非常有用的工具,它允许开发者编写更简洁、更高效的并发代码。理解条件变量的用法和参数对于解决复杂的同步问题至关重要。通过本文的深入分析,您应该对条件变量的用法和注意事项有了更清晰的理解。正确使用条件变量可以显著提高多线程程序的效率和可靠性。

祝大家学习顺利~
如有任何错误,恳请批评指正~~
以上是我通过各种方式得出的经验和方法,欢迎大家评论区留言讨论呀,如果文章对你们产生了帮助,也欢迎点赞收藏,我会继续努力分享更多干货~


🎈关注我的公众号AI Sun可以获取Chatgpt最新发展报告以及腾讯字节等众多大厂面经
😎也欢迎大家和我交流,相互学习,提升技术,风里雨里,我在等你~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员行者孙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值