lesson9: C++多线程
1.线程库
1.1 thread类的简单介绍
C++11 中引入了对 线程的支持 了,使得 C++ 在 并行编程时 不需要依赖第三方库
而且在原子操作中还引入了 原子类 的概念。要使用标准库中的线程,必须包含 < thread > 头文件
| 函数名 | 功能 |
| thread() | 构造一个线程对象,没有关联任何线程函数,即没有启动任何线程 |
| thread(fn, args1, args2, ...) | 构造一个线程对象,并关联线程函数fn,args1,args2,...为线程函数的 参数 |
| get_id() | 获取线程id |
| jionable() | 线程是否还在执行,joinable代表的是一个正在执行中的线程。 |
| jion() | 该函数调用后会 阻塞住线程 ,当该线程结束后,主线程继续执行 |
| detach() | 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离 的线程变为后台线程,创建的线程的"死活"就与主线程无关 |
- 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
- 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程
1.2 线程对象关联线程函数
#include <iostream>
using namespace std;
#include <thread>
void ThreadFunc(int a)
{cout << "Thread1" << a << endl;
}
class TF
{
public:void operator()(){cout << "Thread3" << endl;}
};
int main()
{// 线程函数为函数指针thread t1(ThreadFunc, 10);// 线程函数为lambda表达式thread t2([](){cout << "Thread2" << endl; });// 线程函数为函数对象TF tf;thread t3(tf);t1.join();t2.join();t3.join();cout << "Main thread!" << endl;return 0;
}
- 线程对象可以关联1.函数指针2.lambda表达式3.函数对象
- 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程
1.2.1 注意
- thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以 移动构造 和 移动赋值 ,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
- 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效
- 采用无参构造函数构造的线程对象
- 线程对象的状态已经转移给其他线程对象
- 线程已经调用jion或者detach结束
1.3 线程函数参数

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;void Print(int n, int& x,mutex& mtx)
{for (int i = 0; i < n; ++i){mtx.lock();cout <<this_thread::get_id()<<":"<< i << endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));++x;mtx.unlock();}}int main()
{mutex m;int count = 0;thread t1(Print, 10, ref(count),ref(m));thread t2(Print, 10, ref(count),ref(m);t1.join();t2.join();cout << count << endl;return 0;
}

- 线程函数的参数是先传递给thread的,并以值拷贝的方式拷贝到线程栈空间中的
- 如果不给线程函数的参数不借助 ref函数
- 即使线程参数为 引用类型 ,在线程中修改后也 不能修改外部实参 ,
- 因为其实际引用的是线程栈中的拷贝,而不是外部实参
-
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{mutex mtx;int x = 0;int n = 10;int m;cin >> m;vector<thread> v(m);//v.resize(m);for (int i = 0; i < m; ++i){// 移动赋值给vector中线程对象v[i] = thread([&](){for (int i = 0; i < n; ++i){mtx.lock();cout << this_thread::get_id() << ":" << i << endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));++x;mtx.unlock();}});}for (auto& t : v){t.join();}cout << x << endl;return 0;
}
- 借助lambda表达式中的引用捕捉也可以实现上面那个函数,就可以不用借助ref函数
1.3.1 线程并行 && 并发的讨论

- 并行:任务的同时进行
- 并发: 任务的调动和切换
- 在这个函数中其实是并行的速度更快,因为线程切换十分耗时间
1.4 原子性操作库(atomic)

多线程最主要的问题是共享数据带来的问题 ( 即线程安全 )
当一个或多个线程要 修改 共享数据时,就会产生很多潜在的麻烦
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{mutex mtx;atomic<int> x = 0;// int x = 0;int n = 1000000;int m;cin >> m;vector<thread> v(m);for (int i = 0; i < m; ++i){// 移动赋值给vector中线程对象v[i] = thread([&](){for (int i = 0; i < n; ++i){// t1 t2 t3 t4++x;}});}for (auto& t : v){t.join();}cout << x << endl;return 0;
}


- C++98中传统的解决方式:可以对共享修改的数据加锁保护
- 加锁的问题: 这个线程执行的时候, 其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。
- C++11 中使用atomic类模板,定义出需要的任意原子类型
- 程序员 不需要 对原子类型变量进行 加锁解锁 操作,线程能够对原子类型变量互斥的访问。
-
1.4.1 注意
#include <atomic>
int main()
{atomic<int> a1(0);//atomic<int> a2(a1); // 编译失败atomic<int> a2(0);//a2 = a1; // 编译失败return 0;
}
- 原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,
- 因此在C++11 中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及 operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了
1.5 lock_guard与unique_lock
在 多线程 环境下, 原子性 只能保证 某个变量的安全性
在多线程环境下,而需要保证一段代码的安全性,就只能通过加锁的方式实现
1.5.1 lock_guard
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;//RAII
template<class Lock>
class LockGuard
{
public:LockGuard(Lock& lk):_lock(lk){_lock.lock();cout << "thread:" << this_thread::get_id() << "加锁" << endl;}~LockGuard(){cout << "thread:" << this_thread::get_id() << "解锁" << endl << endl;_lock.unlock();}
private:Lock& _lock;// 成员变量是引用
};int main()
{mutex mtx;atomic<int> x = 0;//int x = 0;int n = 100;int m;cin >> m;vector<thread> v(m);for (int i = 0; i < m; ++i){// 移动赋值给vector中线程对象v[i] = thread([&](){for (int i = 0; i < n; ++i){{lock_guard<mutex> lk(mtx);cout << this_thread::get_id() << ":" << i << endl;}std::this_thread::sleep_for(std::chrono::milliseconds(100));}});}for (auto& t : v){t.join();}cout << x << endl;return 0;
}
- lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装
- 调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。
- lock_guard 的 缺陷 :太单一,用户没有办法对该锁进行控制
1.5.2 unique_lock
- 上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until和unlock
- 修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有 权)、释放(release:返回它所管理的互斥量对象的指针,并释放所有权)
- 获取属性:owns_lock(返回当前对象是否上了锁)、operator bool()(与owns_lock()的功能相 同)、mutex(返回当前unique_lock所管理的互斥量的指针)。
1.6 支持两个线程交替打印,一个打印奇数,一个打印偶数
1.6.1 错误案例
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{int i = 0;int n = 100;mutex mtx;thread t1([&](){while (i < n){mtx.lock();cout << this_thread::get_id() << ":" << i << endl;i += 1;mtx.unlock();}});this_thread::sleep_for(chrono::microseconds(100));thread t2([&](){while (i < n){mtx.lock();cout << this_thread::get_id() << ":" << i << endl;i += 1;mtx.unlock();}});t1.join();t2.join();return 0;
}

- 在线程切换的中间时间也会发现线程竞争抢锁的问题
1.6.2 正确案例


#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<vector>
#include<atomic>
using namespace std;int main()
{int i = 0;int n = 100;mutex mtx;condition_variable cv;// 条件变量bool ready = true;// t1打印奇数thread t1([&](){while (i < n){{unique_lock<mutex> lock(mtx);cv.wait(lock, [&ready](){return !ready; });// 等待线程cout << "t1--" << this_thread::get_id() << ":" << i << endl;i += 1;ready = true;cv.notify_one();// 解除线程等待}//this_thread::yield();this_thread::sleep_for(chrono::microseconds(100));}});// t2打印偶数thread t2([&]() {while (i < n){unique_lock<mutex> lock(mtx);cv.wait(lock, [&ready](){return ready; });cout <<"t2--"<<this_thread::get_id() << ":" << i << endl;i += 1;ready = false;cv.notify_one();}});this_thread::sleep_for(chrono::seconds(3));cout << "t1:" << t1.get_id() << endl;cout << "t2:" << t2.get_id() << endl;t1.join();t2.join();return 0;
}
- cv.wait(lock, [&ready]() {return !ready; });
- 当ready返回的是false时,这个线程就会阻塞
- 阻塞当前线程,并自动调用lock.unlock(),允许其他锁定的线程继续执行
- cv.notify_one();
- 唤醒当前线程并自动调用lock.lock();就只允许自己一个线程执行
1.7 shared_ptr的多线程问题
#include<iostream>
#include<thread>
#include<mutex>
#include<vector>
#include<atomic>
#include<memory>
using namespace std;namespace bit
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pMutex(new mutex){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pMutex(sp._pMutex){AddRef();}void Release(){bool flag = false;_pMutex->lock();if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pMutex->unlock();if (flag)delete _pMutex;}void AddRef(){_pMutex->lock();++(*_pRefCount);_pMutex->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pRefCount = sp._pRefCount;_pMutex = sp._pMutex;AddRef();}return *this;}int use_count(){return *_pRefCount;}~shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pRefCount;// 使用时需要加锁mutex* _pMutex;// 锁指针};
}int main()
{// shared_ptr是线程安全的吗?bit::shared_ptr<double> sp1(new double(1.11));bit::shared_ptr<double> sp2(sp1);mutex mtx;vector<thread> v(2);int n = 100000;for (auto& t : v){t = thread([&](){for (size_t i = 0; i < n; ++i){// 拷贝是线程安全的bit::shared_ptr<double> sp(sp1);// 访问资源不是mtx.lock();(*sp)++;mtx.unlock();}});}for (auto& t : v){t.join();}cout << sp1.use_count() << endl;cout << *sp1 << endl;return 0;
}
-
在多线程中,shared_ptr也应该对自己的引用计数进行加锁处理
- 在多线程中, shared_ptr的拷贝是线程安全的,但访问资源不是,所以访问资源也需要加锁
1.8 单例模式的多线程问题
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class Singleton
{
public:static Singleton* GetInstance(){// 保护第一次,后续不需要加锁// 双检查加锁if (_pInstance == nullptr){unique_lock<mutex> lock(_mtx);if (_pInstance == nullptr){_pInstance = new Singleton;}}return _pInstance;}private:// 构造函数私有Singleton(){};// C++11Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;static Singleton* _pInstance;static mutex _mtx;
};Singleton* Singleton::_pInstance = nullptr;
mutex Singleton::_mtx; int main()
{Singleton::GetInstance();Singleton::GetInstance();return 0;
}
- 在多线程的情况下, 第一次创建对象时也是需要加锁保护的
1.8.1 巧妙的解决方案
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;class Singleton
{
public:static Singleton* GetInstance(){static Singleton _s;// 局部的静态对象,第一次调用时初始化return &_s;}private:// 构造函数私有Singleton() {};// C++11Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;
};int main()
{Singleton::GetInstance();Singleton::GetInstance();return 0;
}
- 局部的静态对象,第一次调用时初始化
- 在C++11之前是不能保证线程安全的
静态对象的构造函数调用初始化并不能保证线程安全的原子性 - C++11的时候修复了这个问题,所以这种写法,只能在支持C++11以后的编译器上玩
相关文章:
lesson9: C++多线程
1.线程库 1.1 thread类的简单介绍 C11 中引入了对 线程的支持 了,使得 C 在 并行编程时 不需要依赖第三方库 而且在原子操作中还引入了 原子类 的概念。要使用标准库中的线程,必须包含 < thread > 头文件 函数名 功能 thread() 构造一个线程对象…...
安卓修改SwitchCompat色值
SwitchCompat控件色值跟系统设置的主题有关,但是主题效果不是能轻易就能改的,因为涉及到整个APP的样式。网上方案基本都是通过修改style文件来改变色值,经过多次尝试修改最终觉得单独修改控件色值比较好。 一、控件属性 //修改开关色值就是最…...
pytorch内存泄漏
问题描述: 内存泄漏积累过多最终会导致内存溢出,当内存占用过大,进程会被killed掉。 解决过程: 在代码的运行阶段输出内存占用量,观察在哪一块存在内存剧烈增加或者显存异常变化的情况。但是在这个过程中要分级确认…...
20230821-字符串相乘-给树命名(unordered_map)
字符串相乘 有两个非负整数字符串num1,num2,计算num1和num2所表达整数的乘积,结果以字符串形式存储。注意:不能通过强制转换方法解题。 示例1: 输入: "4", "3" 输出: "12" …...
[Go版]算法通关村第十二关黄金——字符串冲刺题
目录 题目:最长公共前缀解法1:纵向对比-循环内套循环写法复杂度:时间复杂度 O ( n ∗ m ) O(n*m) O(n∗m)、空间复杂度 O ( 1 ) O(1) O(1)Go代码 解法2:横向对比-两两对比(类似合并K个数组、合并K个链表)复…...
neovim为工作区添加本地clangd配置
1 背景 尝试使用neovim开发stm32,使用clangd作为LSP提供代码补全等功能。 2 思路 使用stm32cubeMX生成一个基于makefile的stm32工程。 使用bear或compiledb基于makefile生成compile_commands.json文件。 为clangd配置--query-driver选项,使其使用arm…...
信号处理--基于EEG脑电信号的眼睛状态的分析
本实验为生物信息学专题设计小项目。项目目的是通过提供的14导联EEG 脑电信号,实现对于人体睁眼和闭眼两个状态的数据分类分析。每个脑电信号的时长大约为117秒。 目录 加载相关的库函数 读取脑电信号数据并查看数据的属性 绘制脑电多通道连接矩阵 绘制两类数据…...
Redis高可用:主从复制详解
目录 1.什么是主从复制? 2.优势 3.主从复制的原理 4.全量复制和增量复制 4.1 全量复制 4.2 增量复制 5.相关问题总结 5.1 当主服务器不进行持久化时复制的安全性 5.2 为什么主从全量复制使用RDB而不使用AOF? 5.3 为什么还有无磁盘复制模式ÿ…...
[Flutter]有的时候调用setState(() {})报错?
先看FlutterSDK的原生类State中有一个变量mounted。 abstract class State<T extends StatefulWidget> with Diagnosticable {/// mounted的作用是,此State对象当前是否在树中。/// 在创建State对象之后,在调用initState之前,框架通过…...
利用屏幕水印学习英语单词,无打扰英语单词学习
1、利用屏幕水印学习英语单词,不影响任何鼠标键盘操作,不影响工作 2、利用系统热键快速隐藏(ALT1键 隐藏与显示) 3、日积月累单词会有进步 4、软件下载地址: 免安装,代码未加密,安全的屏幕水印学习英语…...
开学必备物品清单!这几款优先考虑!
马上就要开学了,同学们也要准备一系列开学用品,方便我们的学习生活,那有哪些数码物品可以在开学前准备的呢,接下来给大家安利几款很不错很实用的数码好物! 推荐一:南卡00压开放式蓝牙耳机 南卡00压开放式…...
聊聊调制解调器
目录 1.什么是调制解调器 2.调制解调器的工作原理 3.调制解调器的作用 4.调制解调器未来发展 1.什么是调制解调器 调制解调器(Modem)是一种用于在数字设备和模拟设备之间进行数据传输的设备。调制解调器将数字数据转换为模拟信号进行传输,…...
Go语言入门指南:基础语法和常用特性(下)
上一节,我们了解Go语言特性以及第一个Go语言程序——Hello World,这一节就让我们更深入的了解一下Go语言的**基础语法**吧! 一、行分隔符 在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ;…...
【MFC常用问题记录】
MFC 记录 MFC的edit control控件显示1.控件添加变量M_edit后:2.控件ID为IDC_EDIT1: 线程函数使用 MFC的edit control控件显示 1.控件添加变量M_edit后: CString str; int x 10; str.Format(_T("%d"),x); M_edit.SetWindowText(str)2.控件ID…...
ThreadLocal内存泄漏问题
引子: 内存泄漏:是指本应该被GC回收的无用对象没有被回收,导致内存空间的浪费,当内存泄露严重时会导致内存溢出。Java内存泄露的根本原因是:长生命周期的对象持有短生命周期对象的引用,尽管短生命周期对象已…...
微服务基础概念【内含图解】
目录 拓展补充: 单体架构 分布式架构 面向服务的体系结构 云原生 微服务架构 什么是微服务? 微服务定义 拓展补充: 单体架构 单体架构:将业务的所有功能集中在一个项目中开发,最终打成一个包部署 优点&#x…...
Dockerfile创建 LNMP 服务+Wordpress 网站平台
文章目录 一.环境及准备工作1.项目环境2.服务器环境3.任务需求 二.Linux 系统基础镜像三.docker构建Nginx1.建立工作目录上传安装包2.编写 Dockerfile 脚本3.准备 nginx.conf 配置文件4.生成镜像5.创建自定义网络6.启动镜像容器7.验证 nginx 四.docker构建Mysql1. 建立工作目录…...
消息中间件篇
消息中间件篇 RabbitMQ 如何保证消息不丢失 面试官: RabbitMQ如何保证消息不丢失 候选人: 嗯!我们当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的,这里面就要求了消息的高可用性,我们要保证消息的不…...
基本定时器
1.简介 1. 基本定时器 TIM6 和 TIM7 包含一个 16 位自动重载计数器 2. 可以专门用于驱动数模转换器 (DAC), 用于触发 DAC 的同步电路 3. 16 位自动重载递增计数器 4. 16 位可编程预分频器 5. 计数器溢出时, 会触发中断/DMA请求 从上往下看 1.开始RCC供给定时器的时钟 RCC_APB1…...
MySQL 中文全文检索
创建索引(MySQL 5.7.6后全文件索引可用WITH PARSER ngram,针对中文,日文,韩文) ALTER TABLE 表 ADD FULLTEXT 索引名 (字段) WITH PARSER ngram;或者CREATE FULLTEXT INDEX 索引名 ON 表 (字段) WITH PARSER ngram; …...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...

