【C++11】多线程创建/互斥详解

目录

    • 1. 背景
    • 2. 线程创建
      • 2.1 使用std::thread类来创建线程
      • 2.2 使用std::async 函数来创建线程
      • 2.3 std::thread和std::async的区别
    • 3. 线程互斥
      • 3.1 互斥体std::mutex
      • 3.2 互斥体包装器std::lock_guard
      • 3.3 条件变量std::condition_variable
      • 3.4 原子类型std::atomic
    • 4. 线程控制自己

1. 背景

在C++11之前,C++标准库并没有提供直接支持多线程的机制。如果我们想在C++程序中实现多线程,需要依赖操作系统提供的线程API。依赖操作系统API有如下缺点↓

  • 导致有跨平台兼容性差:不同的操作系统使用不同的线程API,这使得代码只能在特定的平台上运行)
  • 编程复杂度高:使用操作系统API来进行多线程编程通常比使用高级语言的标准库要复杂得多,需要更多的代码和更精细的控制

操作系统API
在Linux平台上,通常会使用POSIX线程(pthread)API。您需要包含<pthread.h>头文件,并使用pthread_create等函数来创建和管理线程
在Windows平台上,可以使用Windows线程API。您需要包含<windows.h>头文件,并使用CreateThread等函数来创建和管理线程

2. 线程创建

C++11引入了多线程支持,通过使用std::thread类和std::async函数来创建和管理线程。解决了跨平台的问题,并且简化了多线程编程,大大提高了代码的可移植性和可维护性。

2.1 使用std::thread类来创建线程

void myThread_func_without_para() {std::cout << "Functions without parameters as parameters: " << std::endl;
}void myThread_func_with_para(int i) {std::cout << "Functions with parameters as parameters: " << i << std::endl;
}void myThread_funcpoint_without_para() {std::cout << "Functionspoint without parameters as parameters: " << std::endl;
}void myThread_funcpoint_with_para(int i) {std::cout << "Functionspoint with parameters as parameters: " << i << std::endl;
}
    {/*std::thread类的基本所用使用 std::thread 类的构造函数(拷贝、复制函数已经删除,不能使用)创建一个新线程,并将一个可调用对象(如函数、函数指针或 lambda 表达式)作为参数传递给新线程*/std::cout << "Create thread by std::thread" << std::endl;//无参函数作为参数std::thread myThread1(myThread_func_without_para); // 创建一个新线程myThread1并调用myThread_func_without_para函数//有参函数作为参数std::thread myThread2(myThread_func_with_para, 2); // 创建一个新线程myThread2并调用myThread_func_with_para函数//无参函数指针作为参数std::thread myThread3(&myThread_funcpoint_without_para); // 创建一个新线程myThread3并调用myThread_funcpoint_without_para函数//有参函数指针作为参数std::thread myThread4(&myThread_funcpoint_with_para, 4); // 创建一个新线程myThread2并调用myThread_funcpoint_with_para函数//lambda表达式作为参数std::thread myThread5([](int i) {std::cout << "lambda expressions:" << i << std::endl; }, 5);  // 创建一个新线程myThread5并调用 lambda 表达式//堵塞当前线程,等待新线程结束。(join必须有,否则程序可能异常)myThread1.join();myThread2.join();myThread3.join();myThread4.join();myThread5.join();//上述有可能不是按照线程的创建顺序来输出,而是随机的。这是因为多线程是异步方式运行的,谁都有可能先执行完,并不是先创建先执行完。}

2.2 使用std::async 函数来创建线程

int myThread_async(int i) {std::cout << "myThread_async: " << i << std::endl;return i * 10;
}void myThread_getstatus() {std::this_thread::sleep_for(std::chrono::seconds(5));
}
    {/*std::async 函数的基本使用可以使用 std::async 函数来创建一个新线程,并将一个可调用对象作为参数传递给新线程,并返回一个std::future对象,该对象可以用于获取任务的结果或状态。*/std::cout << "Create thread by std::async" << std::endl;//获取线程返回值/*异步启动方式std::launch::async                       一个任务被异步执行std::launch::deferred                    一个任务被延迟执行(延迟到调用 std::future::get() 或 std::future::wait() 时才开始)*/std::future<int> myFuture1 = std::async(std::launch::async, myThread_async, 2); // 创建一个新的异步线程并调用 myThread_async 函数int result = myFuture1.get(); // 阻塞当前线程等待任务结束并获取结果std::cout << "myThread_async result: " << result << std::endl;//获取线程状态(软件的加载画面)/*线程状态std::future_status::ready               操作已经完成std::future_status::timeout             操作超时std::future_status::deferred            操作被延迟*/std::future<void> myFuture2 = std::async(std::launch::async, myThread_getstatus);std::cout << "Please wait 5 seconds:" << std::flush;    // std::flush是C++中的一个操纵符,用于刷新缓冲区。当向标准输出流(如std::cout)写入数据时,数据通常首先存储在内部缓冲区中,然后在缓冲区满或者在一些特定的情况下才被实际写入到设备或者文件中。std::flush操纵符可以用于强制将缓冲区中的数据立即写入到设备(通常是控制台)或文件中。本例中如果不调用std::flush,那么这个字符串可能会在程序结束时才被写入到设备。while (myFuture2.wait_for(std::chrono::seconds(1)) != std::future_status::ready) {std::cout << '.' << std::flush;  //控制台每隔1秒输出一个'.'}std::cout << "myThread_getstatus Finished" << std::endl;}

2.3 std::thread和std::async的区别

  • std::thread 是用于并行执行任务的线程类;std::async 是用于在后台执行任务并返回结果的异步任务函数。
  • 使用 std::thread 时,你需要手动管理线程的生命周期。当你不再需要线程时,你需要显式地调用join或detach来管理,线程生命周期( join() 方法来等待线程结束, detach() 方法让线程在后台运行)。
  • 使用 std::async 时,你不需要手动管理线程的生命周期。当你调用std::async 函数时,它会自动在线程池中创建、调度和执行任务,并返回一个 std::future 对象,你可以使用该对象来获取任务的结果。

总结一下,当你需要更灵活地控制线程的生命周期和并行执行的方式时,可以使用 std::thread。而当你只需要简单地在后台执行任务并获取结果时,可以使用 std::async,因为它会自动管理线程的生命周期。

3. 线程互斥

3.1 互斥体std::mutex

std::mutex mtx1;    //互斥对象
void print_char1(int len, char c) {mtx1.lock();   // 获取互斥对象的锁  for (int i = 0; i < len; i++) {std::cout << c << std::flush;}std::cout << std::endl;mtx1.unlock();   // 释放互斥对象的锁  
}
		/*互斥体std::mutexstd::mutex是C++标准库中提供的互斥对象,用于实现多线程的互斥。简单地说,互斥就是只允许一个线程访问共享资源。*/{std::thread myThread6(print_char1, 10, '#');std::thread myThread7(print_char1, 10, '*');myThread6.join();myThread7.join();/*不加锁,输出结果(交叉混合,每次执行结果不一样)###*********#####*##加锁后,输出结果(不会交叉混合,但输出顺序可能不一样)##########***********/}

3.2 互斥体包装器std::lock_guard

std::mutex mtx2;    //互斥对象
void print_char2(int len, char c) {std::lock_guard<std::mutex> lock(mtx2);   // 创建lock_guard对象,自动锁定互斥体for (int i = 0; i < len; i++) {std::cout << c << std::flush;}std::cout << std::endl;  // 离开作用域,自动解锁互斥体  
}
		/*互斥体包装器std::lock_guardstd::lock_guard是C++11标准库中的一种对象,用于封装互斥体(mutex)的锁定和解锁操作(互斥体包装器)。当 std::lock_guard 对象被创建时(构造函数),它会自动锁定互斥量,当对象离开作用域时,它会自动解锁互斥量(析构函数)。主要用途是简化并发编程中的互斥体管理。它减少了因手动调用互斥体的lock()和unlock()方法而产生的错误和复杂性。使用lock_guard可以确保在任何情况下,包括异常和早期返回,都能正确地释放互斥体。std::unique_lock 是std::lock_guard 的升级加强版- 创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定- std::unique_lock可以随时手动加解锁,而std::lock_guard不能手动加解锁(不手动加解锁的话,同lock_grard一样,构造时自动枷锁,析构时自动释放锁)- 资源占用更多(std::unique_lock切换上下文需要更多资源,而std::lock_guard更轻量级)- 支持移动语义(std::unique_lock支持移动语义,而std::lock_guard不支持)- 与条件变量配合使用时,必须时std::unique_lock,因为它允许你在等待条件变量时解锁互斥对象,等待通知后再重新锁定互斥对象。*/{std::thread myThread8(print_char2, 10, '$');std::thread myThread9(print_char2, 10, '%');myThread8.join();myThread9.join();/*输出结果(不会交叉混合,但输出顺序可能不一样)##########***********/}

3.3 条件变量std::condition_variable

std::mutex mtx3;    //互斥对象
std::condition_variable cv;   //条件对象
bool ready = false;    // 共享条件变量 
void print_char3(int len, char c) {std::unique_lock<std::mutex> lock(mtx3);   // 创建unique_lock对象,自动锁定互斥体while (!ready) {    // 等待条件成立:防止伪唤醒。在多核处理器系统上,由于某些复杂机制的存在,可能发生伪唤醒,即一个线程在没有别的线程发送通知信号时也会被唤醒。因而,当线程唤醒时,检查条件是否成立是必要的。而且,伪唤醒可能多次发生,所以条件检查要在一个循环里进行。cv.wait(lock);   // 堵塞当前线程,而且当线程被阻塞时,该函数会自动调用lock.unlock()释放锁,使得其他被阻塞的线程得以继续执行。直到别的线程调用notify_* 唤醒当前线程后,wait()函数也是自动调用 lock.lock(),重新获得锁}//ready为true的时候,跳出循环for (int i = 0; i < len; i++) {std::cout << c << std::flush;}std::cout << std::endl;// 离开作用域,自动解锁互斥体  
}void setReady() {std::unique_lock<std::mutex> lock(mtx3);   // 创建unique_lock对象,自动锁定互斥体for (int i = 0; i < 100000; i++);ready = true;   // 设置条件成立  cv.notify_all(); // 通知所有等待的线程
}std::mutex mtx4;    //互斥对象
std::condition_variable cv1;   //条件对象 
int value;
void print_value() {std::unique_lock<std::mutex> lock(mtx4);   // 创建unique_lock对象,自动锁定互斥体std::cout << "Please input an integer" << std::endl;while (cv1.wait_for(lock, std::chrono::seconds(1)) == std::cv_status::timeout) {    // 每次等待1秒,如果1秒内没有收到通知,wait_for会返回std::cv_status::timeout,接着程序会打印一个点,然后循环继续等待1秒std::cout << '.' << std::flush;}std::cout << "The input integer is: " << value << std::endl;// 离开作用域,自动解锁互斥体  
}void setValue() {//std::unique_lock<std::mutex> lock(mtx4);   // 创建unique_lock对象,自动锁定互斥体std::cin >> value;  cv1.notify_all(); // 通知所有等待的线程
}
		/*条件变量(std::condition_variable)std::condition_variable是C++11标准库中的一个类,用于在多线程编程中实现线程间的条件同步。条件变量用于在一个线程等待某个条件满足时进行阻塞,并在另一个线程满足条件时通知等待的线程继续执行(即只负责线程的阻塞和唤醒,条件的状态由其他变量或标准来完成)。通常与互斥量(std::mutex)一起使用,以确保线程安全。在等待条件之前,线程必须先锁定互斥量;在等待期间,线程会被阻塞,并释放与条件变量关联的互斥量。当其他线程调用notify_one或notify_all成员函数来通知满足条条件变量时,等待的线程将被唤醒并重新获取互斥量,以继续执行。阻塞线程(wait、wait_for、wait_until)wait	    Wait until notifiedwait_for	Wait for timeout or until notifiedwait_until	Wait until notified or time pointNote重载版本的最后一个参数_Predicate _Pred是一个返回布尔值的函数或表达式,用于在等待条件变量时进行判断。当传入了这个参数的时候,只有为true,且收到通知的情况下(或者指定了等待时间、时间点),wait系列函数才会返回。唤醒阻塞线程(notify_one或notify_all)notify_one	解锁一个线程,如果有多个,则任意一个线程执行,其余继续等待notify_all	解锁所有线程*/{//waitstd::thread myThread10(print_char3, 10, '@');std::thread myThread11(setReady);myThread10.join();myThread11.join();//wait_forstd::thread myThread13(setValue);std::thread myThread12(print_value);myThread12.join();myThread13.join();//生产者/消费者场景/*生产者-消费者问题是一个经典的并发编程问题,涉及到共享固定大小的缓冲区。生产者在缓冲区中添加数据,消费者从缓冲区中消费数据。当缓冲区已满时,生产者应该等待;当缓冲区为空时,消费者应该等待。*//*std::thread myThread14(producer);std::thread myThread15(consumer);myThread14.join();myThread15.join();*/}

3.4 原子类型std::atomic

int number = 0;
std::atomic<int> atomicNumber(0);
void myThread_atomic(int loop) {for (int i = 0; i < loop; i++) {atomicNumber++;number++;}
}
		{/*std::atomic 是C++11引入的一个模板类,用于支持原子操作。它提供了一种线程安全的方式来操作共享变量,避免了多线程环境下的竞态条件。在多线程编程中,多个线程可能会同时访问和修改共享数据,这可能会导致数据竞争(data race)的问题。为了避免数据竞争,可以使用互斥锁(mutex)或条件变量(condition variable)来保护共享数据的访问。然而,这些操作通常比较重量级,会引入额外的开销。为了提高性能,可以使用原子类型。原子类型是一种特殊的数据类型,它在多线程环境中保证对其操作是原子的,即不会被其他线程打断。Notestd::atomic几乎支持所有的内置类型和指针,需要注意的是有些类型的性能并不是很好。*/std::thread myThread16(myThread_atomic, 10000);std::thread myThread17(myThread_atomic, 10000);myThread16.join();myThread17.join();std::cout << "Final number: " << number << std::endl; std::cout << "Final atomicNumber: " << atomicNumber << std::endl; /*输出结果Final number: 19930     // 多线程是同时进行且无序的,所以如果它们同时操作同一个变量,那么肯定会出错Final atomicNumber: 20000   // 原子操作是最小的且不可并行化的操作。所有即使没有加锁,也会像同步进行一样操作atomic对象,从而节省了上锁、解锁的时间消耗*/}

4. 线程控制自己

void myThread_this_thread() {//获取线程idstd::thread::id id = std::this_thread::get_id();std::cout << "thread id: " << id << std::endl;//线程休眠3秒,并且每隔1秒输出一个点std::cout << "thread woke up after 3 seconds: ";for (int i = 0; i < 3; i++) {std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "." << std::flush;}std::cout << std::endl;
}
	{/*std::this_threadget_id		获取线程idyield		提示操作系统让出CPU使用权,让其他线程运行,然后等待操作系统重新调度当前线程并分配CPU使用权继续执行sleep_for	使线程休眠到指定时间sleep_until	使线程休眠到指定的时间点*/std::thread myThread16(myThread_this_thread);myThread16.join();}

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

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

相关文章

JAVA每日面试题(二)

Java 高级面试问题及答案 问题1: 什么是Java内存模型(JMM)&#xff0c;它在多线程编程中扮演什么角色&#xff1f; 答案&#xff1a; Java内存模型&#xff08;JMM&#xff09;是一个抽象的概念&#xff0c;它定义了一组规则&#xff0c;这些规则决定了程序中的变量&#xff…

通用漏洞评估系统CVSS4.0简介

文章目录 什么是CVSS&#xff1f;CVSS 漏洞等级分类历史版本的 CVSS 存在哪些问题&#xff1f;CVSS 4.0改进的“命名法”改进的“基本指标”考虑“OT/IOT”新增的“其他指标”CVSS 4.0存在的问题 Reference: 什么是CVSS&#xff1f; 在信息安全评估领域&#xff0c;CVSS为我们…

2024五一数学建模C题Python代码+结果表数据教学

2024五一数学建模竞赛&#xff08;五一赛&#xff09;C题保姆级分析完整思路代码数据教学 C题 煤矿深部开采冲击地压危险预测 第一问 导入数据 以下仅展示部分&#xff0c;完整版看文末的文章 import numpy as np import pandas as pd import matplotlib.pyplot as plt imp…

基于Springboot的音乐翻唱与分享平台

基于SpringbootVue的音乐翻唱与分享平台设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 音乐资讯 音乐翻唱 在线听歌 后台登录 后台首页 用户管理 音乐资讯管理…

labview强制转换的一个坑

32位整形强制转换成枚举的结果如何&#xff1f; 你以为的结果是 实际上的结果是 仔细看&#xff0c;枚举的数据类型是U16&#xff0c;"1"的数据类型是U32&#xff0c;所以转换产生了不可预期的结果。所以使用强制转换时一定要保证两个数据类型一致&#xff0c;否则…

Python绝对路径及命令行执行路径的写法收录

Python绝对路径及命令行执行路径的写法 在Python中&#xff0c;以使用字符串来表示绝对路径。为了避免转义字符的问题&#xff0c;可以使用原始字符串&#xff08;raw string&#xff09;来表示路径。 直接r 后面路径是单或反斜杠均可&#xff0c;r让转义字符不起作用&#x…

【JavaEE网络】TCP套接字编程详解:从概念到实现

目录 TCP流套接字编程ServerSocket APISocket APITCP回显客户端服务器 TCP流套接字编程 TCP用的协议比UDP更多&#xff0c;可靠性 提供的api主要有两个类ServerSocket&#xff08;给服务器使用的socket&#xff09;&#xff0c;Socket&#xff08;既会给服务器使用也会给客户…

SQL server 使用教程

1.安装和配置SQL Server&#xff1a; 下载安装包&#xff1a;首先&#xff0c;你需要下载SQL Server的安装包。可以从Microsoft官方网站获取最新版的安装包链接&#xff0c;然后下载适用于你的操作系统的版本。 安装SQL Server&#xff1a;双击运行下载的安装包&#xff0c;按…

CentOS7安装MySQL8.3(最新版)踩坑教程

安装环境说明 项值系统版本CentOS7 &#xff08;具体是7.9&#xff0c;其他7系列版本均可&#xff09;位数X86_64&#xff0c;64位操作系统MySQL版本mysql-8.3.0-1.el7.x86_64.rpm-bundle.tar 实际操作 官网下载安装包 具体操作不记录&#xff0c;相关教程很多。 mkdir /o…

锂电池SOH预测 | 基于BP神经网络的锂电池SOH预测(附matlab完整源码)

锂电池SOH预测 锂电池SOH预测完整代码锂电池SOH预测 锂电池的SOH(状态健康度)预测是一项重要的任务,它可以帮助确定电池的健康状况和剩余寿命,从而优化电池的使用和维护策略。 SOH预测可以通过多种方法实现,其中一些常用的方法包括: 容量衰减法:通过监测电池的容量衰减…

QT5制做两个独立窗口

目录 增加第二个窗口 主窗口文件添加一个私有成员为子窗口 定义两个槽函数和 关联按钮和子窗口和主窗口 添加子窗口成员 子窗口处理函数 补充回顾 增加第二个窗口 1、 2、 3 主窗口文件添加一个私有成员为子窗口 在mainwidget.h文件 同时添加两个槽&#xff1b;来处理…

Linux 系统上安装 NVIDIA 驱动程序失败(X server问题)

报错信息&#xff1a; ERROR: You appear to be running an X server; please exit X before installing. For further details, please see the section INSTALLING THE NVIDIA DRIVER in the README available on the Linux driver download page at www.nvidia.com. ERROR: …

Docker: 如何不新建容器 修改运行容器的端口

目录 一、修改容器的映射端口 二、解决方案 三、方案 一、修改容器的映射端口 项目需求修改容器的映射端口 二、解决方案 停止需要修改的容器 修改hostconfig.json文件 重启docker 服务 启动修改容器 三、方案 目前正在运行的容器 宿主机的3000 端口 映射 容器…

vue2实现面包屑功能

目录 1. store/index.js 2. router/index.js 3. Header.vue 在Vue 2中实现面包屑导航是一种常见的前端实践,它可以帮助用户了解当前页面在网站结构中的位置,并快速导航到上一级或根目录。以下是使用Vue 2实现面包屑导航的基本步骤: 1. store/index.js state中定义一个面…

python 关键字(await)

2、await 在Python的异步编程中,await关键字扮演着至关重要的角色。对于初学者来说,理解await的使用和背后的概念可能有些困难,但对于有经验的开发者来说,掌握它则是编写高效、响应性强的代码的关键。下面我将从基础到高级,逐步解析await关键字。 基础知识:await是什么?…

NLP(11)--词向量

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 one-hot 编码 i love u [1,2,3] 词向量训练目标&#xff1a; 如果两个词在文本出现&#xff0c;它的前后出现的词相似&#xff0c;则这两个词语义相似 cbow(基于窗口预测词)缺点 :输出层是vocab_size 会很大 收敛速度会很慢…

【综述】多核处理器芯片

文章目录 前言 Infineon处理器 AURIX™系列 TC399XX-256F300S 典型应用 开发工具 参考资料 前言 见《【综述】DSP处理器芯片》 Infineon处理器 AURIX™系列&#xff0c;基于TriCore内核&#xff0c;用于汽车和工业领域。 XMC™系列&#xff0c;基于ARM Cortex-M内核&…

test4282

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

2024五一杯数学建模A题思路分析-钢板最优切割路径问题

文章目录 1 赛题选题分析 2 解题思路3 最新思路更新 1 赛题 A题 钢板最优切割路径问题 提高钢板下料切割过程中的工作效率&#xff0c;是模具加工企业降低成本和增加经济效益的重要途径&#xff0c;其中钢板切割的路径规划是钢板切割过程的一个关键环节。 钢板切割就是使用特殊…

2024 五一杯高校数学建模邀请赛(C题)| 煤矿深部开采冲击地压危险预测 |建模秘籍文章代码思路大全

铛铛&#xff01;小秘籍来咯&#xff01; 小秘籍团队独辟蹊径&#xff0c;构建了这一题的详细解答哦&#xff01; 为大家量身打造创新解决方案。小秘籍团队&#xff0c;始终引领着建模问题求解的风潮。 抓紧小秘籍&#xff0c;我们出发吧~ 让我们看看五一杯的C题&#xff01; 完…