IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11 多线程编程之条件变量std:: condition_variable、wait()、notify_one()、notify_all() -> 正文阅读

[C++知识库]C++11 多线程编程之条件变量std:: condition_variable、wait()、notify_one()、notify_all()

1、为何引入条件变量 ?

  • 解决while不断循环收发消息,让它只有消息到来时才进行处理。大大减少CPU的使用率和提高程序效率;
  • 在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量;
  • c++11提供的 condition_variable 类是一个同步原语,它能够阻塞一个或者多个线程,直到另一线程修改共享变量并通知 condition_variable。也可以把它理解为信号通知机制,一个线程负责发送信号,其他线程等待该信号的触发。

2、条件变量 std:: condition_variable

std:: condition_variable是条件变量,实际上是个类,是一个与条件相关的类,说白了就是等待一个条件的达成。这个类是需要和互斥量来配合工作的,使用时定义一个该类对象即可。

实例代码:
线程A:循环等待一个条件满足,但若条件不满足会休眠在条件变量,并不会占用CPU。
线程B:专门往消息队列扔消息(数据),然后通知其它线程。

  • ?当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 锁住当前线程。当前线程会一直被阻塞,直到另外一个线程相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程;
  • std::condition_variable 对象通常使用 std::unique_lock<std::mutex> 来等待,如果需要使用另外的 lockable 类型,可以使用 std::condition_variable_any 类,本文后面会讲到 std::condition_variable_any 的用法;

注意:

  • std :: condition_variable 仅适用于 std::unique_lock, 此限制允许在某些平台上获得最大效率。 std :: condition_variable_any 提供可与任何BasicLockable对象一起使用的条件变量,例如std :: shared_lock。
  • 修改共享变量 ready/processed 时需要锁,共享变量用于避免虚假唤醒
  • ? cv.wait 第一个参数必须是 unique_lock,因为它内部会执行 unlock和lock,如果需要设置超时,使用 wait_for/wait_until

特别注意,如果不使用共享变量,当通知线程在接收线程准备接收之前发送通知,接收线程将要永远阻塞了。这里,共享变量已经置位,所以它也能避免丢失唤醒。

3、notify_one()与notify_all()

notify_one()notify_all() 常用来唤醒阻塞的线程;

  • notify_one():因为只唤醒等待队列中的第一个线程;不存在锁争用,所以能够立即获得锁;其余的线程不会被唤醒,需要等待再次调用notify_one()或者notify_all();
  • notify_all():会唤醒所有等待队列中阻塞的线程,存在锁争用,只有一个线程能够获得锁。那其余未获取锁的线程接着会怎么样?会阻塞?还是继续尝试获得锁?答案是会继续尝试获得锁(类似于轮询),而不会再次阻塞。当持有锁的线程释放锁时,这些线程中的一个会获得锁。而其余的会接着尝试获得锁。

看下面的例子:

#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) // 如果标志位ready=false,!ready = true时, 则执行cv.wait(lck),当前线程被阻塞;
        cv.wait(lck); /*当全局标志位ready=true 之后,不执行 cv.wait(lck),
                      且另一个线程有 cv.notify_all()时,线程被唤醒, 继续往下执行打印线程编号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;
}

运行结果为:

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

输出表明所有线程被唤醒,然后依旧获得了锁。

如果将go()中的cv.notify_all()改为cv.notify_one(),运行结果为:

10 threads ready to race...
thread 1

输出表明只有有一个线程被唤醒,然后该线程释放锁,这时锁已经处理非锁定状态,但是其余线程依旧处于阻塞状态。

  • 当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程,该函数会自动调用?lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。
  • 所以,线程阻塞在condition_variable时,它是等待notify_one()或者notify_all()来唤醒,而不是等待锁可以被锁定来唤醒。线程被唤醒后,会通过轮询方式获得锁,获得锁前也一直处理运行状态,不会被再次阻塞。

参考资料


std::condition_variable
why do I need std::condition_variable?

https://blog.csdn.net/guotianqing/article/details/104017649?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.control&spm=1001.2101.3001.4242

https://blog.csdn.net/weixin_44517656/article/details/112096777?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.control&spm=1001.2101.3001.4242

https://blog.csdn.net/qq_38210354/article/details/107168532?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-1.control

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-22 13:21:43  更:2021-08-22 13:23:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 4:49:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计