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. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程

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 注意

  1. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以 移动构造 移动赋值 ,即将一个
    线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
  2. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效
    1. 采用无参构造函数构造的线程对象
    2. 线程对象的状态已经转移给其他线程对象
    3. 线程已经调用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_guardunique_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_guard 不同的是, unique_lock 更加的灵活,提供了更多的成员函数
  • 上锁/解锁操作locktry_locktry_lock_fortry_lock_untilunlock
  • 修改操作:移动赋值、交换(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以后的编译器上玩

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

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

相关文章

探工业互联网的下一站!腾讯云助力智造升级

引言 数字化浪潮正深刻影响着传统工业形态。作为第四次工业革命的重要基石&#xff0c;工业互联网凭借其独特的价值快速崛起&#xff0c;引领和推动着产业变革方向。面对数字化时代给产业带来的机遇与挑战&#xff0c;如何推动工业互联网的规模化落地&#xff0c;加速数字经济…

【C语言】数组概述

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;C语言 &#x1f525;该篇将带你了解 一维数组&#xff0c;二维数组等相关知识。 目录&#xff1a; &#x1f4d8;前言&#xff1a;&#x1f…

KaiwuDB CTO 魏可伟:回归用户本位,打造“小而全”的数据库

8月16日&#xff0c;KaiwuDB 受邀亮相第十四届中国数据库技术大会 DTCC 2023。KaiwuDB CTO 魏可伟接受大会主办方的采访&#xff0c;双方共同围绕“数据库架构演进、内核引擎设计以及不同技术路线”展开深度探讨。 以下是采访的部分实录 ↓↓↓ 40 多年前&#xff0c;企业的数…

html动态爱心代码【三】(附源码)

目录 前言 特效 内容修改 完整代码 总结 前言 七夕马上就要到了&#xff0c;为了帮助大家高效表白&#xff0c;下面再给大家带来了实用的HTML浪漫表白代码(附源码)背景音乐&#xff0c;可用于520&#xff0c;情人节&#xff0c;生日&#xff0c;表白等场景&#xff0c;可直…

OrienterNet: visual localization in 2D public maps with neural matching 论文阅读

论文信息 题目&#xff1a;OrienterNet: visual localization in 2D public maps with neural matching 作者&#xff1a;Paul-Edouard Sarlin&#xff0c; Daniel DeTone 项目地址&#xff1a;github.com/facebookresearch/OrienterNet 来源&#xff1a;CVPR 时间&#xff1a…

win10 下运行 npm run watch-poll问题

背景&#xff1a;在本地练习laravel项目&#xff0c;windows 宝塔环境&#xff08;之前装过ubuntu子系统&#xff0c;很慢&#xff0c;就放弃了。有知道的兄弟说下&#xff0c;抱拳&#xff09;。以下命令我是在本地项目中用git bash里运行的&#xff0c;最好用管理员权限打开你…

Django实现音乐网站 ⒀

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是推荐页-推荐排行榜、推荐歌手功能开发。 目录 推荐页开发 推荐排行榜 单曲表增加播放量 表模型增加播放量字段 执行表操作 模板中显示外键对应值 表模型外键设置 获取外键对应模型值 推荐排行榜视图 推…

函数式编程

函数式编程 函数式编程思想&#xff1a;对方法中的数据进行了什么操作 优点&#xff1a;代码简介、便于理解、易于并发编程 1.Lambda表达式 JDK8中的语法糖&#xff0c;可以对某些匿名内部类的写法进行简化 使用条件&#xff1a;匿名内部类是一个接口&#xff0c;并且接口只…

GraphScope,开源图数据分析引擎的领航者

文章首发地址 GraphScope是一个开源的大规模图数据分析引擎&#xff0c;由Aliyun、阿里巴巴集团和华为公司共同开发。GraphScope旨在为大规模图数据处理和分析提供高性能、高效率的解决方案。 Github地址&#xff1a; https://github.com/alibaba/GraphScope GraphScope 的重…

【电商领域】Axure在线购物商城小程序原型图,抖音商城垂直电商APP原型

作品概况 页面数量&#xff1a;共 60 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;网上商城、品牌自营商城、商城模块插件 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本作品为品牌自营网上商城…

logstash配置文件

input { kafka { topics > “xxxx” bootstrap_servers > “ip:port” auto_offset_reset > “xxxx” group_id > “xxxx” consumer_threads > 3 codec > “json” } } filter { grok { match > { “message” > ‘%{IP:client_ip} - - [%{HTTPDATE:…

线性代数的学习和整理---番外1:EXCEL里角度,弧度,三角函数

目录 1 角的度量&#xff1a;角度和弧度 1.1 角度 angle 1.1.1 定义 1.1.2 公式 1.1.2 角度取值范围 1.2 弧长和弦长 1.3 弧度 rad 1.3.1 弧长和弧度定义的原理 1.3.2 定义 1.3.3 取值范围 1.3.4 取值范围 1.4 角度&#xff0c;弧度的换算 1.5 EXCEL里进行角度和…

STL list基本用法

目录 list的使用构造函数和赋值重载迭代器(最重要)容量相关插入删除元素操作reversesortuniqueremovesplice list的底层实际是双向链表结构 list的使用 构造函数和赋值重载 构造函数说明list()无参构造list (size_type n, const value_type& val value_type())构造的li…

安全学习DAY18_信息打点-APP资产搜集

信息打点-APP资产&静态提取&动态抓包&动态调试 文章目录 信息打点-APP资产&静态提取&动态抓包&动态调试本节知识&思维导图本节使用到的链接&工具 如何获取目标APP从名称中获取APP从URL获取APP APP搜集资产信息APP提取信息分类信息提取方式信息…

怎么管理运营私域流量?

私域流量管理是当今企业运营的重要议题&#xff0c;对于企业发展和品牌建设具有不可忽视的作用。然而&#xff0c;管理私域流量并不是一项轻松的任务&#xff0c;需要我们采取科学有效的措施&#xff0c;才能取得良好的效果。 首先&#xff0c;私域流量管理需要建立清晰的目标。…

Linux系统安全——NAT(SNAT、DNAT)

目录 NAT SNAT SNAT实际操作 DNAT DNAT实际操作 NAT NAT: network address translation&#xff0c;支持PREROUTING&#xff0c;INPUT&#xff0c;OUTPUT&#xff0c;POSTROUTING四个链 请求报文&#xff1a;修改源/目标IP&#xff0c; 响应报文&#xff1a;修改源/目标…

HTTP 握手过程

HTTP 握手过程 TCP 建立连接 3 次握手 客户端请求连接服务器服务器响应成功客户端回应服务器准备开始连接 TCP 结束连接 4 次挥手 客户端向服务器发送&#xff0c;断开请求服务器向客户端发送&#xff0c;还有数据没有传输完毕&#xff0c;请稍等服务器向客户端发送&#x…

基于微信小程序的中医体质辨识文体活动的设计与实现(Java+spring boot+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于微信小程序的中医体质辨识文体活动的设计与实现&#xff08;Javaspring bootMySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java s…

解决Pandas KeyError: “None of [Index([...])] are in the [columns]“问题

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

java八股文面试[JVM]——JVM参数

参考&#xff1a;JVM学习笔记&#xff08;一&#xff09;_卷心菜不卷Iris的博客-CSDN博客 堆参数调优入门 jdk1.7&#xff1a; jdk1.8&#xff1a; 面试题&#xff1a;给定-Xms Xmx -Xmn 问 最大的eden区域是多少M。 常用JVM参数 怎么对jvm进行调优&#xff1f;通过参数配…