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

C++ 多线程std::thread以及条件变量和互斥量的使用

前言

  • 本文章主要介绍C++11语法中std::thread的使用,以及条件变量和互斥量的使用。

std::thread介绍

构造函数

  • std::thread 有4个构造函数
  • // 默认构造函,构造一个线程对象,在这个线程中不执行任何处理动作
    thread() noexcept;// 移动构造函数。将 other 的线程所有权转移给新的thread 对象。之后 other 不再表示执行线程。
    // 线程对象只可移动,不可复制
    thread( thread&& other ) noexcept;// 创建线程对象,并在该线程中执行函数f中的业务逻辑,args是要传递给函数f的参数
    template< class F, class... Args > 
    explicit thread( F&& f, Args&&... args );// 使用=delete显示删除拷贝构造, 不允许线程对象之间的拷贝
    thread( const thread& ) = delete;
    
  • 通过以下代码演示下如何构造函数的使用
    •   #include <iostream>#include <thread>#include <chrono>void threadFunc2() {std::cout << "enter threadFunc2" << std::endl;}void threadFunc3(int data) {std::cout << "enter threadFunc3, data: " << data << std::endl;}class CThread4 {public:void threadFunc4(const char * data) {std::cout << "enter threadFunc4, data: " << data << std::endl;}};void threadFunc5() {for (int i = 0; i < 5; i++) {std::cout << "enter threadFunc5" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}int main() {// 默认构造std::thread th1;// 线程中执行函数std::thread th2(threadFunc2);th2.join();// 线程中执行带参函数std::thread th3(threadFunc3, 10010);th3.join();CThread4 ct4;// 线程中执行类成员函数std::thread th4(&CThread4::threadFunc4, &ct4, "hello world");th4.join();std::thread th5_1(threadFunc5);// 使用移动构造std::thread th5_2(std::move(th5_1));th5_2.join();// 执行lambda表达式std::thread th6([] {std::cout << "enter threadFunc6" << std::endl;});th6.join();system("pause");return 0;}
      
  • 执行结果
    •   enter threadFunc2enter threadFunc3, data: 10010enter threadFunc4, data: hello worldenter threadFunc5enter threadFunc5enter threadFunc5enter threadFunc5enter threadFunc5enter threadFunc6请按任意键继续. . .
      

成员函数

  • // 获取线程ID
    std::thread::id get_id() const noexcept;
    // 阻塞当前线程,直至调用join的子线程运行结束
    void join();
    // 将执行线程从线程对象中分离,允许独立执行。
    void detach();
    // 判断主线程和子线程的关联状态
    bool joinable() const noexcept;
    // 如果 *this 仍然有一个关联的运行中的线程,则调用 std::terminate()。
    // 否则,将 other 的状态赋给 *this 并将 other 设置为默认构造的状态。
    thread& operator=( thread&& other ) noexcept;
    
  • 通过代码看下如何使用成员函数
    •   #include <iostream>#include <thread>#include <chrono>void threadFunc3(int data) {std::cout << "enter threadFunc3, data: " << data << std::endl;}void threadFunc4(int data) {std::cout << "start threadFunc4, data: " << data << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "end threadFunc4, data: " << data << std::endl;}void threadFunc5(int data) {std::cout << "start threadFunc5, data: " << data << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "end threadFunc5, data: " << data << std::endl;}int main() {{// 线程中执行带参函数std::thread th3(threadFunc3, 10010);std::cout << "th3 id: " << th3.get_id() << std::endl;// 此刻th3线程与主线程有关联std::cout << "th3 joinable: " << th3.joinable() << std::endl;th3.join();std::cout << "th3 id: " << th3.get_id() << std::endl;// 线程执行结束,此刻th3线程与主线程无关联std::cout << "th3 joinable: " << th3.joinable() << std::endl;}{std::thread th5(threadFunc5, 10050);// 如果不想在主线程中等待子线程,可以使用detach。// 这样即便主线程运行结束,子线程依旧会执行// 实际使用时不建议这样做th5.detach();}system("pause");return 0;}
      
  • 执行结果
    •   enter threadFunc3, data: 10010th3 id: 12820th3 joinable: 1th3 id: 0th3 joinable: 0start threadFunc5, data: 10050请按任意键继续. . . end threadFunc5, data: 10050
      

条件变量

  • 条件变量是C++11提供的一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程。
  • C++11中的条件变量叫 condition_variable,需要配合std::unique_lock<std::mutex>使用。
  • 先看以下一段代码
    •   #include <iostream>#include <thread>#include <chrono>#include <mutex>#include <condition_variable>int g_cnt = 0;// 定义互斥量std::mutex g_mutex;// 定义条件变量std::condition_variable g_cond;void threadFunc1() {while (g_cnt != 50) {std::this_thread::sleep_for(std::chrono::milliseconds(1));}std::cout << "threadFunc1 g_cnt: " << g_cnt << std::endl;}void threadFunc2() {while (g_cnt < 100) {g_cnt++;std::this_thread::sleep_for(std::chrono::milliseconds(1));}}int main() {{std::thread th1(threadFunc1);std::thread th2(threadFunc2);th1.join();th2.join();std::cout << "g_cnt: " << g_cnt << std::endl;}system("pause");return 0;}
      
  • 线程2对g_cnt进行递增操作,线程1在g_cnt等于50时退出循环并打印结果。但这个流程有一个问题,比如线程1某次访问g_cnt时,值为49,下一次再访问时,值可能为50,也可能为51。如果g_cnt值为51,那这个条件永远都不会满足,循环也就永远无法结束。并且线程1中,我们只想获取g_cnt等于50这个状态,没必要每次都去访问,这也会耗费系统资源。
  • 使用条件变量做以下修改
    •   #include <iostream>#include <thread>#include <chrono>#include <mutex>#include <condition_variable>int g_cnt = 0;// 定义互斥量std::mutex g_mutex;// 定义条件变量std::condition_variable g_cond;bool g_flag = false;void threadFunc1() {std::unique_lock<std::mutex> lock(g_mutex);while (!g_flag) {// 阻塞等待,等待被唤醒g_cond.wait(lock);}std::cout << "threadFunc1 g_cnt: " << g_cnt << std::endl;}void threadFunc2() {while (g_cnt < 100) {g_cnt++;if (g_cnt == 50) {g_flag = true;// 唤醒阻塞的线程g_cond.notify_one();}std::this_thread::sleep_for(std::chrono::milliseconds(1));}}int main() {{std::thread th1(threadFunc1);std::thread th2(threadFunc2);th1.join();th2.join();std::cout << "g_cnt: " << g_cnt << std::endl;}system("pause");return 0;}
      
  • 这样就可以保证线程1的条件肯定可以满足。

线程互斥

  • 控制线程对共享资源的访问。比如写文件时,不能读文件,读文件时,不能写文件。
  • C++11提供了4种互斥锁
    • std::mutex:独占的互斥锁,不能递归使用。
    • std::timed_mutex:带超时的独占互斥锁,不能递归使用。在获取互斥锁资源时增加了超时等待功能。
    • std::recursive_mutex:递归互斥锁,不带超时功能。允许同一线程多次获得互斥锁。
    • std::recursive_timed_mutex:带超时的递归互斥锁。
  • 分析以下这段代码的输出结果
    •   #include <iostream>#include <thread>#include <chrono>#include <mutex>int g_cnt = 0;void threadFunc(int num) {for (int i = 0; i < num; i++) {g_cnt++;std::this_thread::sleep_for(std::chrono::milliseconds(1));}}int main() {{// 线程中执行带参函数std::thread th1(threadFunc, 100);std::thread th2(threadFunc, 100);th1.join();th2.join();std::cout << "g_cnt: " << g_cnt << std::endl;}system("pause");return 0;}
      
  • 我们期望的g_cnt输出结果为200,但实际上g_cnt很大概率不是200而是小于200。这是由于没有对共享资源g_cnt进行加锁保护,这会导致数据竞争。两个线程可能同时访问g_cnt,导致某个线程的++操作被另一个线程覆盖。
  • 对上面代码做下修改,对共享资源g_cnt进行加锁保护。
    •   #include <iostream>#include <thread>#include <chrono>#include <mutex>int g_cnt = 0;// 定义互斥量std::mutex g_mutex;void threadFunc(int num) {for (int i = 0; i < num; i++) {// 加锁g_mutex.lock();g_cnt++;// 解锁g_mutex.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(1));}}int main() {{// 线程中执行带参函数std::thread th1(threadFunc, 100);std::thread th2(threadFunc, 100);th1.join();th2.join();std::cout << "g_cnt: " << g_cnt << std::endl;}system("pause");return 0;}
      
  • 加锁后,就可以保证g_cnt的结果为200。
  • 上面代码有这样一种风险。如果加锁后,中途退出而忘记解锁,就会导致死锁现象。
    •   void threadFunc(int num) {for (int i = 0; i < num; i++) {// 加锁g_mutex.lock();g_cnt++;if (i == 50) {break;}// 解锁g_mutex.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(1));}}
      
  • C++ 11 提供了一种模板类 std::lock_guard,可以简化互斥锁的写法。调用构造时加锁,离开作用域时解锁。不用手动加解锁,大大提高了安全性。
  • 实现代码如下。即便中途退出,也不会出现死锁现象。
    •   void threadFunc(int num) {for (int i = 0; i < num; i++) {// 加锁std::lock_guard<std::mutex> lock(g_mutex);g_cnt++;if (i == 50) {break;}std::this_thread::sleep_for(std::chrono::milliseconds(1));}}
      

参考

  • https://en.cppreference.com/w/cpp/thread/thread

相关文章:

C++ 多线程std::thread以及条件变量和互斥量的使用

前言 本文章主要介绍C11语法中std::thread的使用&#xff0c;以及条件变量和互斥量的使用。 std::thread介绍 构造函数 std::thread 有4个构造函数 // 默认构造函&#xff0c;构造一个线程对象&#xff0c;在这个线程中不执行任何处理动作 thread() noexcept;// 移动构造函…...

新华三H3CNE网络工程师认证—子接口技术

子接口&#xff08;subinterface&#xff09;是通过协议和技术将一个物理接口&#xff08;interface&#xff09;虚拟出来的多个逻辑接口。在VLAN虚拟局域网中&#xff0c;通常是一个物理接口对应一个 VLAN。在多个 VLAN 的网络上&#xff0c;无法使用单台路由器的一个物理接口…...

【MySQL】InnoDB内存结构

目录 InnoDB内存结构 主要组成 缓冲池 缓冲池的作用 缓冲池的结构 缓冲池中页与页之间连接方式分析 缓冲池如何组织数据 控制块初始化 页面初始化 缓冲池中页的管理 缓冲区淘汰策略 查看缓冲池信息 总结 变更缓冲区-Chang Buffer 变更缓冲区的作用 主要配置选项…...

基于大数据爬虫数据挖掘技术+Python的网络用户购物行为分析与可视化平台(源码+论文+PPT+部署文档教程等)

#1024程序员节&#xff5c;征文# 博主介绍&#xff1a;CSDN毕设辅导第一人、全网粉丝50W,csdn特邀作者、博客专家、腾讯云社区合作讲师、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老…...

蓝桥杯每日真题 - 第19天

题目&#xff1a;&#xff08;费用报销&#xff09; 题目描述&#xff08;13届 C&C B组F题&#xff09; 解题思路&#xff1a; 1. 问题抽象 本问题可以看作一个限制条件较多的优化问题&#xff0c;核心是如何在金额和时间约束下选择最优方案&#xff1a; 动态规划是理想…...

CentOS7.9.2009的yum更换vault地窖保险库过期源,epel的archive归档源 笔记241117

CentOS7.9.2009的yum更换vault地窖保险库过期源,epel的archive归档源 笔记241117 备份 /etc/yum.repos.d 文件夹 tempUri/etc/yum.repos.d ; sudo cp -a $tempUri $tempUri.$(date %0y%0m%0d%0H%0M%0Sns%0N).bak清空 /etc/yum.repos.d 文件夹 sudo rm -rf /etc…...

Spark SQL大数据分析快速上手-完全分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…...

Java面试题2024-Java基础

Java基础 1、 Java语言有哪些特点 1、简单易学、有丰富的类库 2、面向对象&#xff08;Java最重要的特性&#xff0c;让程序耦合度更低&#xff0c;内聚性更高&#xff09; 3、与平台无关性&#xff08;JVM是Java跨平台使用的根本&#xff09; 4、可靠安全 5、支持多线程 2、…...

局域网协同办公软件,2024安全的协同办公软件推荐

在2024年&#xff0c;随着数字化转型的深入和远程工作需求的增加&#xff0c;协同办公软件已成为企业提升工作效率、优化沟通流程的重要工具。 以下是一些值得推荐的安全的协同办公软件&#xff1a; 钉钉 功能全面&#xff1a;钉钉是一款综合性极强的企业级协同软件&#xff…...

osg、osgearth简介及学习环境准备

一、osg简介&#xff08;三维场景图渲染与调度引擎&#xff09; OSG是Open Scene Graphic 的缩写&#xff0c;OSG于1997年诞生于以为滑翔机爱好者之手&#xff0c;Don burns 为了对滑翔机的飞行进行模拟&#xff0c;对openGL的库进行了封装&#xff0c;osg的雏形就这样诞生了&…...

nodejs基于微信小程序的云校园的设计与实现

摘 要 相比于传统的校园管理方式&#xff0c;智能化的管理方式可以大幅提高校园的管理效率&#xff0c;实现了云校园管理的标准化、制度化、程序化的管理&#xff0c;有效地防止了云校园信息的不规范管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、准确地…...

uni-app快速入门(十)--常用内置组件(下)

本文介绍uni-app的textarea多行文本框组件、web-view组件、image图片组件、switch开关组件、audio音频组件、video视频组件。 一、textarea多行文本框组件 textarea组件在HTML 中相信大家非常熟悉&#xff0c;组件的官方介绍见&#xff1a; textarea | uni-app官网uni-app,un…...

golang基础

在 Go 中字符串是不可变的&#xff0c;例如下面的代码编译时会报错&#xff1a; cannot assign to s[0] 但如果真的想要修改怎么办呢&#xff1f;下面的代码可以实现&#xff1a; var s string "hello" s [ 0 ] c s : "hello" c : [] b…...

Selenium + 数据驱动测试:从入门到实战!

引言 在软件测试中&#xff0c;测试数据的多样性和灵活性对测试覆盖率至关重要。而数据驱动测试&#xff08;Data-Driven Testing&#xff09;通过将测试逻辑与数据分离&#xff0c;极大地提高了测试用例的可维护性和可扩展性。本文将结合Selenium这一流行的测试工具&#xff0…...

LLaMA与ChatGLM选用比较

目录 1. 开发背景 2. 目标与应用 3. 训练数据 4. 模型架构与规模 5. 开源与社区支持 6. 对话能力 7. 微调与应用 8. 推理速度与资源消耗 总结 LLaMA(Large Language Model Meta AI)和 ChatGLM(Chat Generative Language Model)都是强大的大型语言模型,但它们有一…...

GPTZero:高效识别AI生成文本,保障学术诚信与内容原创性

产品描述 GPTZero 是一款先进的AI文本检测工具&#xff0c;专为识别由大型语言模型&#xff08;如ChatGPT、GPT-4、Bard等&#xff09;生成的文本而设计。它通过分析文本的复杂性和一致性&#xff0c;判断文本是否可能由人类编写。GPTZero 已经得到了超过100家媒体机构的报道&…...

C/C++ 优化,strlen 示例

目录 C/C optimization, the strlen examplehttps://hallowed-blinker-3ca.notion.site/C-C-optimization-the-strlen-example-108719425da080338d94c79add2bb372 揭开优化的神秘面纱... 让我们来谈谈 CPU 等等&#xff0c;SIMD 是什么&#xff1f; 为什么 strlen 是一个很…...

【动手学深度学习Pytorch】1. 线性回归代码

零实现 导入所需要的包&#xff1a; # %matplotlib inline import random import torch from d2l import torch as d2l import matplotlib.pyplot as plt import matplotlib import os构造人造数据集&#xff1a;假设w[2, -3.4]&#xff0c;b4.2&#xff0c;存在随机噪音&…...

深入理解PyTorch中的卷积层:工作原理、参数解析与实际应用示例

深入理解PyTorch中的卷积层&#xff1a;工作原理、参数解析与实际应用示例 在PyTorch中&#xff0c;卷积层是构建卷积神经网络&#xff08;CNNs&#xff09;的基本单元&#xff0c;广泛用于处理图像和视频中的特征提取任务。通过卷积操作&#xff0c;网络可以有效地学习输入数…...

DataGear 5.2.0 发布,数据可视化分析平台

DataGear 企业版 1.3.0 已发布&#xff0c;欢迎体验&#xff01; http://datagear.tech/pro/ DataGear 5.2.0 发布&#xff0c;图表插件支持定义依赖库、严重 BUG 修复、功能改进、安全增强&#xff0c;具体更新内容如下&#xff1a; 重构&#xff1a;各模块管理功能访问路径…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...