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

news2024/11/17 9:47:27

前言

  • 本文章主要介绍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 threadFunc2
        enter threadFunc3, data: 10010
        enter threadFunc4, data: hello world
        enter threadFunc5
        enter threadFunc5
        enter threadFunc5
        enter threadFunc5
        enter threadFunc5
        enter 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: 12820
        th3 joinable: 1
        th3 id: 0
        th3 joinable: 0
        start 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2242089.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

15分钟学 Go 第 53 天 :社区资源与学习材料

第53天&#xff1a;社区资源与学习材料 目标 了解Go语言官方资源掌握社区重要学习平台学会利用开源项目学习构建个人知识体系 一、Go语言官方资源汇总 资源类型网址说明Go官网golang.org官方文档、下载、教程Go Blogblog.golang.org技术博客、最新特性介绍Go Playgroundpla…

丹摩征文活动 |【前端开发】HTML+CSS+JavaScript前端三剑客的基础知识体系了解

前言 &#x1f31f;&#x1f31f;本期讲解关于HTMLCSSJavaScript的基础知识&#xff0c;小编带领大家简单过一遍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 …

【Python · PyTorch】卷积神经网络(基础概念)

【Python PyTorch】卷积神经网络 CNN&#xff08;基础概念&#xff09; 0. 生物学相似性1. 概念1.1 定义1.2 优势1.2.1 权重共享1.2.2 局部连接1.2.3 层次结构 1.3 结构1.4 数据预处理1.4.1 标签编码① One-Hot编码 / 独热编码② Word Embedding / 词嵌入 1.4.2 归一化① Min-…

机器学习-36-对ML的思考之机器学习研究的初衷及科学研究的期望

文章目录 1 机器学习最初的样子1.1 知识工程诞生(专家系统)1.2 知识工程高潮期1.3 专家系统的瓶颈(知识获取)1.4 机器学习研究的初衷2 科学研究对机器学习的期望2.1 面向科学研究的机器学习轮廓2.2 机器学习及其应用研讨会2.3 智能信息处理系列研讨会2.4 机器学习对科学研究的重…

激光slam学习笔记5---ubuntu2004部署运行fastlivo踩坑记录

背景&#xff1a;看看fastlivo论文&#xff0c;觉得挺有意思的&#xff0c;就本地部署跑跑看看效果。个人环境&#xff0c;ubuntu20.04。 一、概要 由于依赖比较多&#xff0c;个人构建工作空间&#xff0c;使用catkin_make编译 src├── FAST-LIVO├── livox_ros_driver…

多模态大模型开启AI社交新纪元,Soul App创始人张璐团队亮相2024 GITEX GLOBAL

随着AI在全球范围内的加速发展和广泛应用,各行业纷纷在此领域发力。作为全球最大的科技盛会之一,2024年的GITEX GLOBAL将目光再次聚焦于人工智能的飞速发展,吸引了超过6700家来自各个领域的企业参与。在这样的背景下,Soul App作为国内较早将AI技术应用于社交领域的平台,首次亮相…

67页PDF |埃森哲_XX集团信息发展规划IT治理优化方案(限免下载)

一、前言 这份报告是埃森哲_XX集团信息发展规划IT治理优化方案&#xff0c;报告中详细阐述了XX集团如何优化IT治理结构以适应新的要求。报告还分析了集团管控模式的变化&#xff0c;提出了六大业务中心的差异化管控策略&#xff0c;并探讨了这些变化对IT治理模式的影响。报告进…

基于java Springboot高校失物招领平台

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

[C++] 智能指针

文章目录 智能指针的使用原因及场景分析为什么需要智能指针&#xff1f;异常抛出导致的资源泄漏问题分析 智能指针与RAIIC常用智能指针 使用智能指针优化代码优化后的代码优化点分析 析构函数中的异常问题解决方法 RAII 和智能指针的设计思路详解什么是 RAII&#xff1f;RAII 的…

Git回到某个分支的某次提交

1.切换到需要操作的分支&#xff08;<branch-name>是分支名称&#xff09;。 命令如下&#xff1a; git checkout <branch-name> 2.获取代码的提交记录 。命令如下&#xff1a; git log 按q退出当前命令对话。 获取到某次提交或者合并的hash值&#xff08;下文…

掌握 Spring Boot 的最佳方法 – 学习路线图

在企业界&#xff0c;人们说“Java 永垂不朽&#xff01;”。但为什么呢&#xff1f;Java 仍然是开发企业应用程序的主要平台之一。大型公司使用企业应用程序来赚钱。这些应用程序具有高可靠性要求和庞大的代码库。根据Java开发人员生产力报告&#xff0c;62% 的受访开发人员使…

1. Django中的URL调度器 (项目创建与简单测试)

1. 创建 Django 项目 运行以下命令创建一个名为 blog_project 的 Django 项目&#xff1a; django-admin startproject blog_project2. 创建博客应用 Django 中&#xff0c;项目可以包含多个应用。创建一个名为 blog 的应用&#xff1a; cd blog_project python manage.py …

frp内网穿透介绍安装教程

文章目录 前言一、安装二、测试使用总结 前言 内网穿透&#xff08;Port Forwarding&#xff09;是将公网上的IP地址映射到内部网络中的一台计算机的某个端口上&#xff0c;以便外部网络可以访问该计算机中运行的应用程序。内网穿透技术可以通过一些开源工具来实现&#xff0c…

主界面获取个人信息客户端方

主界面获取个人信息客户端方 前言 上一集我们完成了websocket身份验证的内容&#xff0c;那么这一集开始我们将要配合MockServer来完成主界面获取个人信息的内容。 需求分析 我们这边是完成客户端那方的内容&#xff0c;当客户端登录成功之后&#xff0c;我们就要从服务器获…

redis实现消息队列的几种方式

一、了解 众所周知&#xff0c;redis是我们日常开发过程中使用最多的非关系型数据库&#xff0c;也是消息中间件。实际上除了常用的rabbitmq、rocketmq、kafka消息队列&#xff08;大家自己下去研究吧~模式都是通用的&#xff09;&#xff0c;我们也能使用redis实现消息队列。…

单片机智能家居火灾环境安全检测

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 在现代社会&#xff0c;火灾安全始终是人们关注的重点问题。随着科技的不…

【目标检测】用YOLOv8-Segment训练语义分割数据集(保姆级教学)

前言 这篇教程会手把手带你用 YOLOv8-Segment 搭建一个属于自己的分割任务项目。从环境配置到数据集准备&#xff0c;再到模型训练和测试&#xff0c;所有步骤都有详细说明&#xff0c;适合初学者使用。你将学会如何安装必要的软件&#xff0c;标注自己的数据&#xff0c;并使…

mac2019环境 Airflow+hive+spark+hadoop本地环境安装

1 环境介绍 本地安装可分为两个部分&#xff0c;mac软件环境&#xff0c; python开发环境 ps: 安装过程参考chatgpt、csdn文章 1.1 mac软件环境 目标安装的的软件是hive、apache-spark、hadoop&#xff0c;但是这三个软件又依赖java(spark依赖&#xff09;、ssh&#xff08…

1.7 JS性能优化

从输入url到页面加载完成都做了些什么 输入 URL - 资源定位符 http://www.zhaowa.com - http 协议 域名解析 https://www.zhaowa.com > ip 1. 切HOST&#xff1f; > 浏览器缓存映射、系统、路由、运营商、根服务器 2. 实际的静态文件存放&#xff1f; 大流量 > 多个…

【Ansible常用命令+模块+Playbook+Roles】

Ansible 一、命令1.1 常用命令 二、模块2.1 shell模块2.2 复制模块2.3 用户模块2.4 软件包管理2.5 服务模块2.6 文件模块2.7 收集模块2.8 fetch2.9 cron2.10 group2.11 script2.12 unarchive 三、YAML Roles3.1 目录结构3.2 文件内容tasks/main.yamlnginx.conf.j2vars/main.yam…