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

线程同步---条件变量

学习自《深入应用C++11 代码优化与工程级应用》

过程中许多不懂的点问了智谱清言

条件变量

条件变量是C++11提供的另外一个用于等待的同步机制,它能阻塞一个或多个线程,直到收到另一个线程发出的通知或超时,才会唤起当前阻塞的线程。条件变量需要和互斥量配合起来用。

C++11提供了两种条件变量:

condition_variable:

配合std::unique_lock<std::mutex>进行wait操作。

condition_variable_any:

和任意带有lock,unlock语义的mutex搭配使用,比较灵活,但效率比condition_variable差一些。

condition_variable_any比condition_variable更灵活,因为它更通用,对所有的锁都适用,而condition_variable性能更好。 

 条件变量的使用过程如下:

拥有条件变量的线程获取互斥量。

循环检测某个条件,如果条件不满足,则阻塞直到条件满足;如果条件满足,则向下进行。

某个线程满足条件执行完之后调用notify_one或notify_all唤醒一个或所有的等待线程。

--------- 

std::unique_lock:

它提供了对互斥量(如std::mutex)的灵活锁定策略。std::unique_lock对象是可移动的,但不是可复制的,这意味着你可以通过移动语义来转移所有权,但不能复制它。这意味着你不能使用复制构造函数或复制赋值函数来复制std::unique_lock对象,但可以通过移动构造函数或移动赋值函数来转移对象的所有权。

 

 

 

调用release后,unique_lock对象的owns_lock()将返回false 

#include <iostream>
#include <mutex>std::mutex mtx;int main() {std::unique_lock<std::mutex> ul(mtx); // 锁定互斥量// 释放互斥量的所有权std::mutex* p_mutex = ul.release();// 此时 unique_lock 对象不再拥有互斥量if (ul.owns_lock()) {std::cout << "unique_lock still owns the mutex." << std::endl;} else {std::cout << "unique_lock no longer owns the mutex." << std::endl;}// 手动锁定互斥量p_mutex->lock();// ... 在这里执行一些操作 ...// 手动解锁互斥量p_mutex->unlock();return 0;
}

 

--------- 

 学习std::condition_variable

std::condition_variable要求与std::unique_lock<std::mutex>或std::lock_guard<std::mutex>一起使用

 

 

 

在调用 wait() 系列函数时,必须确保互斥量已经被当前线程锁定。

std::condition_variable 的 wait() 方法用于使当前线程进入等待状态,直到条件变量被通知,或者某个特定的条件成立。在调用 wait() 时,线程必须已经锁定了与之关联的互斥量(通常是 std::unique_lock,并且 wait() 会在内部解锁互斥量,然后在等待期间阻塞线程。一旦条件变量被通知,wait() 将重新锁定互斥量,并返回。 

void wait(unique_lock<mutex>& __lock);

使当前线程进入等待状态,直到条件变量被通知或者虚假唤醒发生。

函数在开始等待之前会自动释放这个锁,这样其他线程就可以获取这个锁并可能更改条件变量的状态。当 wait 函数返回时(无论是因为条件变量被通知还是虚假唤醒),它将重新获取这个锁。

当 wait函数被调用时,以下步骤会发生:

  1. 当前线程释放 __lock 所持有的互斥量。
  2. 当前线程进入等待状态,等待条件变量被其他线程通过 notify_one() 或 notify_all() 唤醒。
  3. 当线程被唤醒时,它重新获取互斥量,然后 wait函数返回。

--------- 

虚假唤醒: 

虚假唤醒(Spurious Wakeup)是指在多线程编程中,一个线程在等待某些条件时,即使条件尚未满足,线程也可能被唤醒。这种现象在基于条件变量的同步机制中是可能的,并且是预期之内的行为。

虚假唤醒之所以会发生,是因为操作系统和硬件层面的原因,如中断、信号处理、线程调度策略等。虚假唤醒不是错误,而是程序员在使用条件变量时必须考虑的一种可能性。

以下是一些处理虚假唤醒的方法: 

1.循环检查条件: 当线程从 wait() 函数返回时,应该总是在一个循环中检查条件是否真正满足,而不是假设 wait() 返回就一定意味着条件已经满足。这种模式通常称为“等待-通知循环”或“自旋锁”。

示例代码:

std::unique_lock<std::mutex> lock(mtx);
while (!condition) {  // 使用 while 而不是 ifcv.wait(lock);   // 可能会发生虚假唤醒
}
// 条件满足,继续执行后续操作

 2.使用条件变量的其他形式: C++11 及更高版本提供了 wait() 函数的重载版本,允许你传递一个lambda表达式或函数对象来检查条件,从而避免显式循环。例如:

std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return condition; });
// 条件满足,继续执行后续操作

在这个例子中,即使发生了虚假唤醒,lambda 表达式会再次检查条件,直到条件真正满足。

虚假唤醒并不是问题,因为通过正确使用条件变量,你可以确保程序的正确性。记住,当使用条件变量时,总是应该使用循环来检查条件,即使文档中没有明确说明虚假唤醒的存在。这是编写健壮多线程代码的最佳实践。 

---------------

void wait(unique_lock<mutex>& __lock, _Predicate __p)

 谓词(predicate)作为参数。谓词是一个可调用的对象(例如,函数、函数对象或 lambda 表达式),它用于检查等待条件是否满足

在这个实现中,wait 方法会执行以下步骤:

  1. 检查谓词 __p 是否返回 true。如果返回 true,则表示等待条件已经满足,线程可以继续执行。
  2. 如果谓词返回 false,则调用不带谓词的 wait 方法,这将使线程进入等待状态,直到条件变量被另一个线程通过 notify_one() 或 notify_all() 方法通知,或者发生虚假唤醒。
  3. 如果线程被唤醒(无论是由于通知还是虚假唤醒),它会再次检查谓词。如果谓词仍然返回 false,线程将继续等待。

这种方法确保了即使在发生虚假唤醒的情况下,线程也不会继续执行,直到谓词条件真正满足。这是处理条件变量时避免竞态条件和确保线程安全的关键。 

在使用条件变量时,始终使用带有谓词的 wait 方法是一种好的实践,因为它可以简化代码并减少错误。 

---------

    void notify_one() noexcept;

当 notify_one() 被调用时,它会随机选择一个正在等待的线程(如果有的话)并唤醒它。

 noexcept 

 noexcept是一个关键字,用于指定某个函数或对象构造函数不会抛出异常。如果在函数或构造函数声明中使用了 noexcept,则表示该函数承诺不会因为运行时异常而终止执行。

--------- 

为什么使用 noexcept? 

使用 noexcept 有几个好处:

  1. 性能提升:某些操作(如异常处理)可能需要额外的代码来处理异常情况。如果函数承诺不会抛出异常,编译器可以优化代码,从而可能提高性能。

  2. 简化资源管理:对于析构函数和释放资源的函数,使用 noexcept 可以确保资源管理更加安全和高效。

  3. 增强异常安全:通过明确哪些函数可能抛出异常,哪些函数不会,可以更好地设计程序的异常安全策略。

注意事项 

  • 如果你声明了一个函数为 noexcept,但它抛出了异常,程序会调用 std::terminate 来立即终止执行。
  • 在移动构造函数和移动赋值运算符中使用 noexcept 是一个好的实践,因为它们通常不应该抛出异常。


http://www.mrgr.cn/news/64490.html

相关文章:

  • 西门子编程软件报错“Step 7 basic”找不到许可证问题
  • 群控系统服务端开发模式-应用开发-菜单功能开发
  • Observer 观察者模式
  • ComfyUI和Photoshop相结合,PS内实现:文生图,图生图,高清放大,局部重绘,面部修复,设计师福音
  • 近期学习前端的心得
  • 【使用 Python 和 ADB 检查 Android 设备的 Wi-Fi 状态】
  • 整理 【 DBeaver 数据库管理工具 】的一些基础使用
  • 使用TypeORM进行数据库操作
  • 6.2、实验二:默认路由
  • SQLI LABS | Less-26 GET-Error Based-All Your SPACES And COMMENTS Belong To Us
  • 【毫米波雷达(五)】车载毫米波雷达SDA售后标定流程
  • YOLOv6-4.0部分代码阅读笔记-yolo_lite.py
  • 01 DSA-- 二叉树
  • springboot 自动装配和bean注入原理及实现
  • C++ | Leetcode C++题解之第528题按权重随机选择
  • 【CSS in Depth 2 精译_056】8.4 CSS 的新特性——原生嵌套(Nesting)+ 8.5 本章小结
  • C语言 | Leetcode C语言题解之第528题按权重随机选择
  • adb 远程调试,手动修改 adb 调试授权信息
  • C++/list
  • 【A】【Maven项目热部署】将Maven项目热部署到远程tomcat服务器上
  • C语言:初识入门篇
  • oracle 月份加减一个月
  • Pinctrl子需要中client端使用pinctrl过程的驱动分析
  • 计算机毕业设计Hadoop+大模型地震预测系统 地震数据分析可视化 地震爬虫 大数据毕业设计 Spark 机器学习 深度学习 Flink 大数据
  • C语言300行-投篮
  • 2、Qt6 Quick 会转的小风车