当前位置: 首页 > backend >正文

条件变量底层实现原理

在并发编程中,条件变量用来控制线程之间的同步,总的来说条件变量相关的操作主要有两个——wait和notify,在写博客时我有这样一些疑问:

1.wait操作是如何实现的,它是如何让线程进入睡眠的

2.notiy操作是如何做到通知正在等待的线程的,并且如何做到notify单个线程和notify所有线程的,如果是notify所有线程,那么这些被唤醒的线程可以并行执行吗(多核情况下)

3.为什么在调用wait操作时,需要传入mutex互斥锁,而notify时却不用。

4.c++中的条件变量为什么要配合unique_lock<mutex>锁而不是std::mutex使用或者lock_guard<mutex>

本文从以下几个方面对条件变量进行深入的理解

1.C++中的condition_variable实现原理

2.linux pthread库中的pthread condition variable实现原理

3.从操作系统源码的角度理解条件变量是如何实现的

4.有哪些错误的条件变量实现方式

条件变量使用的例子

例1

参考:C++ std::condition_variable 条件变量类探索:解锁条件变量的底层原理_std 条件变量-CSDN博客

include<iostream>// std::coutinclude<thread>// std::threadinclude<mutex>// std::mutex, std::unique_lockinclude<condition_variable>// std::condition_variableusingnamespace std;mutex mtx;                          // 互斥量
condition_variable cv;              // 条件变量bool ready = false;                 // 标志量voidprint_id(int id){unique_lock<mutex> lck(mtx);    // 上锁while (!ready) {cv.wait(lck);               // 线程等待直到被唤醒(释放锁 + 等待,唤醒,在函数返回之前重新上锁)}cout << "thread " << id << '\n';
}voidgo(){unique_lock<mutex> lck(mtx);    // 上锁ready = true;cv.notify_all();                // 唤醒所有正在等待(挂起)的线程(在这里面要释放锁,为了在wait函数返回之前能成功的重新上锁)
}intmain(){thread threads[10];for (int i = 0; i<10; ++i) {threads[i] = thread(print_id, i);}cout << "10 threads ready to race...\n";go();for (auto& th : threads) {th.join();}return0;
}

从这个例子可以看出,在调用notify和wait时,都需要进行加锁操作,所以mutex互斥锁锁住的其实是ready这个变量。到这里第二个问题中的“被唤醒的线程能否并行执行”也就有答案了,答案是不能,因为每个线程是需要获得互斥锁unique_lock<mutex>的。

对于第四个问题,答案是出于安全考虑,主要是防止程序员忘记对std::mutex进行unlock操作,特别是在一些异常处理中。至于为什么不用lock_guard[std::mutex](std::mutex),这个在后面介绍wait底层实现时说明

C++中的condition_variable实现原理

参考:C++ std::condition_variable 条件变量类探索:解锁条件变量的底层原理_std 条件变量-CSDN博客

c++ 11引入了条件变量,在gcc中的实现位于libstdc++-v3/include/std/condition_variable路径下(为了方面阅读,本文对源码略有修改)

class condition_variable{__condvar _M_cond;public:typedef __gthread_cond_t *native_handle_type;condition_variable() noexcept;~condition_variable() noexcept;condition_variable(const condition_variable &) = delete;condition_variable &operator=(const condition_variable &) = delete;voidnotify_one() noexcept;voidnotify_all() noexcept;voidwait(unique_lock<mutex> &__lock) {_M_cond.wait(*__lock.mutex());}template <typename _Predicate>voidwait(unique_lock<mutex> &__lock, _Predicate __p){while (!__p())wait(__lock);}}

可以看到wait/notify操作实际上是调用__condvar的接口,可以看到wait操作中获取了互斥量,从而在底层调用互斥量的lock/unlock接口,而lock_guard属于RAII 类,在构造函数中加锁,在析构函数中解锁,除此之外无法对其管理的互斥量进行lock/unlock接口。这里解答了第四个问题,由于wait底层操作中需要对互斥量进行lock/unlock操作,lock_guard并不满足这样的需求。

__condvar实际上是对glibc中pthread condition variable的一层包装,如下:

class __condvar{public:...voidwait(mutex &__m){int __e __attribute__((__unused__)) = __gthread_cond_wait(&_M_cond, __m.native_handle());__glibcxx_assert(__e == 0);}voidwait_until(mutex &__m, timespec &__abs_time){__gthread_cond_timedwait(&_M_cond, __m.native_handle(), &__abs_time);}...voidnotify_one() noexcept{int __e __attribute__((__unused__)) = __gthread_cond_signal(&_M_cond);__glibcxx_assert(__e == 0);}voidnotify_all() noexcept{int __e __attribute__((__unused__)) = __gthread_cond_broadcast(&_M_cond);__glibcxx_assert(__e == 0);}protected:
#ifdef __GTHREAD_COND_INITpthread_cond_t _M_cond = __GTHREAD_COND_INIT;
#elsepthread_cond_t _M_cond;
#endif};

可以看到,wait/notify操作底层主要是调用glibc的pthread condition variable接口。

glibc中的pthread condition variable实现原理

在网上找了一篇博客,从源码角度分析了条件变量的实现原理,细节暂时看不懂,以后再看吧深入了解glibc的条件变量_牛客网

看了这篇博客可以解答第三个问题,对于wait操作,其底层实现是需要对互斥量进行lock/unlock操作的,因此需要传入unique_lock

TODO

http://www.xdnf.cn/news/921.html

相关文章:

  • 2025 年职业院校技能大赛网络建设与运维赛项Docker赛题解析
  • Spark SQL概述(专业解释+生活化比喻)
  • Redis专题
  • NLP高频面试题(四十九)大模型RAG常见面试题解析
  • 基于大模型的血栓性外痔全流程风险预测与治疗管理研究报告
  • 检测IP地址欺诈风险“Scamalytics”
  • M2N2 解读
  • 卷积神经网络--手写数字识别
  • Spark-SQL(四)
  • 微服务架构下数据库范式的失效与反范式设计的崛起
  • 将长循环任务拆分成多个小步骤,以非阻塞的方式执行,在裸机环境下的实现方法
  • 【第16届蓝桥杯C++C组】--- 2025
  • vue2练习项目 家乡特色网站—前端静态网站模板
  • 8. ROS中常见命令
  • Vue中如何优雅地阻止特定标签的移除并恢复其原始位置
  • 代码随想录算法训练营Day32
  • 在线查看【免费】 txt, xml(渲染), md(渲染), java, php, py, js, css 文件格式网站
  • CFIS-YOLO:面向边缘设备的木材缺陷检测轻量级网络解析
  • 从零开始了解数采(十七)——工业数据清洗
  • 【计算机网络】第五章 局域网技术
  • 你学会了些什么220622?--搭建UI自动化
  • 设计模式深度总结:概念、实现与框架中的应用
  • 【Linux】调试工具gdb的认识和使用指令介绍(图文详解)
  • 深入解析 Linux 文件系统中的软硬链接:从原理到实践
  • CF2096F Wonderful Impostors
  • QT:Qt5 串口模块 (QSerialPort) 在 VS2015 中正确关闭串口避免被占用
  • (14)VTK C++开发示例 --- 将点投影到平面上
  • C++ vector 核心功能解析与实现
  • Spring-AOP分析
  • Uniapp:view容器(容器布局)