OPC Client第11讲【C++并发与多线程1】:基本概念和理解;Thread线程库的基本使用;线程传参详解
上面这个视频对应的笔记链接如下:
上面这个视频对应文档链接如下(关注up主微信,在微信公众号上打开的):
一、并发、进程、线程的基本概念和综述
先看下面的笔记链接
c++11并发于多线程_2章_1节_哔哩哔哩_bilibili
C++11并发与多线程笔记(1) 并发基本概念及实现,进程、线程基本概念_c++11进程-CSDN博客
1、CPU配置:物理核心(内核)、逻辑处理器、超线程
自己电脑的CPU配置为:14核20线程
1>物理核心(内核)
- 指CPU中实际存在的独立计算单元,每个核心可同时执行一条指令流。
- CPU有14个物理核心,意味着可并行处理14个独立任务
2>逻辑处理器 / 逻辑核心:
- 超线程 = 1 个物理核心 → 操作系统看到 2 个逻辑处理器
- 超线程技术可以让一个物理核心被操作系统识别为两个逻辑处理器。
- 我的 CPU 是 14 核 20 线程,说明并非所有核心都开启了超线程(可能是 10 核开启超线程 = 20 线程,另外 4 核关闭超线程)。
3>超线程
2、并发、线程、进程
1>并发
C++11并发与多线程笔记(1) 并发基本概念及实现,进程、线程基本概念_c++11进程-CSDN博客
2>可执行程序
- 磁盘上的一个文件,windows下,扩展名为.exe;
- linux下,ls -la,rwx(可读可写可执行)
3>进程:就是运行起来的可执行程序
- 运行一个可执行程序,在windows下,可双击;在linux下,./文件名
- 进程,一个可执行程序运行起来了,就叫创建了一个进程。
- 进程就是运行起来的可执行程序。
在VisualStudio中,写好代码后点击“生成解决方案”(如下图),即编译好了代码,在Debug文件夹中创建了一个.exe的可执行程序(如下下图)
如下图,运行两个.exe程序(即连续两次点击下图红框)
在任务管理器中能看到两个进程,如下图。
4>线程:用来执行代码的【理解为一条代码的执行通路】
C++11并发与多线程笔记(1) 并发基本概念及实现,进程、线程基本概念_c++11并发与多线程 胡胡浩特-CSDN博客
1》主线程:随着进程自动启动/结束(main函数)
《1》每个进程(执行起来的可执行程序),都有唯一的一个主线程
《2》当执行可执行程序时,产生一个进程后,这个主线程就随着这个进程默默启动起来了
ctrl+F5运行这个程序的时候,实际上是进程的主线程来执行(调用)这个main函数中的代码
线程:用来执行代码的。线程这个东西,可以理解为一条代码的执行通路
2》多线程(并发)
- 除了主线程之外,可以通过写代码来创建其他线程,其他线程走的是别的道路,甚至去不同的地方
- 每创建一个新线程,就可以在同一时刻,多干一个不同的事(多走一条不同的代码执行路径)
- 线程并不是越多越好,每个线程,都需要一个独立的堆栈空间(大约1M),线程之间的切换要保存很多中间状态;
- 切换也会耗费本该属于程序运行的时间
二、并发的实现方法:两个或者更多的任务(独立的活动)同时发生(进行)
必须使用多线程的案例
1、多进程并发:通过多个进程实现并发
- 比如账号服务器一个进程,游戏服务器一个进程。
- 服务器进程之间存在通信(同一个电脑上:管道,文件,消息队列,共享内存);(不同电脑上:socket通信技术)
2、多线程并发【推荐】:在单独的进程中,写代码创建除了主线程之外的其他线程来实现并发
线程:感觉像是轻量级的进程。每个进程有自己独立的运行路径,但一个进程中的所有线程共享地址空间(共享内存);全局变量、全局内存、全局引用都可以在线程之间传递,所以多线程开销远远小于多进程。
- 共享内存带来新问题,数据一致性问题;线程A,线程B;
多进程并发和多线程并发可以混合使用,但建议优先考虑多线程技术。
- 本课程中只讲多线程并发技术
三、C++11新标准线程库【多线程能够跨平台】
以往
- windows:CreateThread(), _beginthread(),_beginthreadexe()创建线程;
- linux:pthread_create()创建线程;不能跨平台
- 临界区,互斥量
- POSIX thread(pthread):跨平台,但要做一番配置,也不方便
C++11【后面都用这个讲】
- 从C++11新标准,C++语言本身增加对多线程的支持,意味着可移植性(跨平台),这大大减少开发人员的工作量
四、C++11 Thread线程库的基本使用
1、创建线程
2、传递参数
3、t.join()
【常用】:
等待线程完成
非常重要。去掉很容易出错【见五、2、】
区别下述4、
4、t.detach():分离线程,让线程在后台运行
如下图,thread1这个线程使用 thread1.detach()方法分离线程,让它在后台运行。
但是控制台不会输出任何东西。
- 因为后台运行的线程还没有执行输出cout时,主线程就已经运行结束了。
- 主线程运行结束后,同时这个进程就会自动结束。进程结束就会导致后台运行的线程也会自动结束。
5、joinable():判断此线程是否可以被 join()或 detach()
joinable()方法返回一个布尔值,如果线程可以被 join()或 detach(),则返回true,否则返回 false。
如果我们试图对一个不可加入的线程调用 join()或 detach(),则会抛出一个 std::system_error异常。
6、常见错误
1>忘记等待线程完成或分离线程
2>访问共享数据时没有同步
3>异常传递问题
五、线程传参详解
1、传递临时变量的问题
如下图,在 std::thread
构造函数尝试解析参数时,编译器就发现无法将右值 1
绑定到 foo
所需的 int&
参数上。这个绑定失败发生在编译期。
C++ 有一条核心规则【见2>1》】:
不能将非 const 的左值引用绑定到右值上。
thread构造函数里禁用了隐式类型转换,会对参数进行完美转发【见2>2》《3》】。
foo
函数期望一个int&
(int的引用)。- 您传递的
1
是一个纯右值,即一个临时的、不可寻址的整数字面量。 - C++标准禁止将非
const
左值引用绑定到右值上【见2>1》】。int& ref = 1;
这样的语句本身就是非法的。 - 因此,
std::thread
在尝试构造时,无法将右值1
绑定到foo
函数需要的int&
参数上,编译器会直接报错。
1>解决方式1【推荐】:std::ref:给函数传递引用类型
2>解决方式2:使用 const
引用
但是参数用了const后,就不能在线程函数中修改参数的值了。
1》左值无法引用右值,但 const
左值引用可以!
2》【额外补充】T&&:右值引用,
只能绑定到 右值
T&&
是 C++11 引入的 右值引用,它是实现 移动语义和 完美转发 的核心机制。
- 右值:临时对象、字面量、表达式结果等“即将销毁”的值。
《1》右值引用:只能绑定到 右值
《2》移动语义【略】:右值引用允许我们“移动”资源,而不是“拷贝”资源
《3》完美转发:能够将函数参数原样地、无损地传递给另一个函数,保持其左值/右值属性、const/volatile 属性等
2、传递指针或引用指向局部变量的问题
1>如果用t.detach()
虽然 t.join()
阻塞了主线程,确保了线程 t
一定在 x
被销毁之前完成了对 x
的访问。
【如果用t.detach()就会报错
】
为什么仍然被认为是“有问题”或“不推荐”?
尽管有 join()
保证了安全,但这种模式仍然被认为是脆弱的、容易出错的,原因如下:
- 容易被破坏:只要有人修改代码,比如把
t.join();
注释掉,或者在join()
之前不小心让main
函数提前返回,问题就立刻暴露。 std::thread
的析构要求:如果一个std::thread
对象在未被join()
或detach()
的情况下被销毁(比如离开作用域),程序会调用std::terminate()
直接终止,这是非常严重的错误。join()
是避免这种情况的必要手段,但依赖它来保证数据生命周期是一种“把安全建立在纪律上”的做法,不够健壮。- 代码意图不清晰:传递一个指向栈变量的指针,会让其他阅读代码的人立刻警觉,因为它看起来就很危险。使用值传递或智能指针能让意图更清晰、更安全。
2>引用指向局部变量
如下图,a是test函数的局部变量。test函数结束后,a会被释放掉。
所以可能出现在线程 t
启动前,a
已被释放掉
,导致foo
访问了已释放的内存,行为未定义。
解决办法:法1>在test函数中增加阻塞;法2>把a设置成全局变量,如下图。
3、必须在t.join()之后释放参数资源
1>传递指针或引用指向已释放的内存的问题
与2、的问题一样,只是这里是我们自己主动释放的。
在线程 t
启动前,ptr
已被delete
,导致foo
访问了已释放的内存,行为未定义。【有可能运行代码成功:即存储ptr的地址被释放后,不知道里面填的是什么东西,foo也能输出】
解决方法:去掉delete
2>类成员函数作为入口函数,类对象被提前释放
与3>的问题一样,只是把int型的对象换成了一个类而已。
1》非静态成员函数有一个隐式的 this
参数
为什么上图的线程t要传&obj?func()没有要接受的参数啊
虽然 func()
在定义时没有参数,但它是一个非静态成员函数。在 C++ 中,每一个非静态成员函数都隐式地接受一个 this
指针作为第一个参数。
- 调用一个类的成员函数需要一个对象,现在的a就是A的一个实例对象
2》智能指针: std::make_shared
通过在对象的生命周期内 自动管理内存分配和释放,来帮助程序员避免内存泄漏、悬空指针等问题。
它们在超出作用域时会自动调用析构函数,释放其所指向的内存。
- 注意下图,obj是指针类型,所以传参数的时候不用&
4、入口函数为类的私有成员函数
如下图,foo是私有成员函数,在类的外部不能被访问。
1>哪些能访问私有成员函数?
私有成员函数的访问权限遵循严格的规则。它们只能在类的内部被访问,具体包括以下几种情况:
1》类的其他成员函数
例如同一个类中的其他成员函数(无论是公有、保护还是私有的)都可以直接调用该私有成员函数。
2》类的友元函数
被声明为类的友元的函数,即使在类外部定义,也可以访问私有成员函数。
友元:目的就是让一个函数或者类 访问另一个类中私有成员。
3》类的友元类
被声明为友元的类,其成员函数可以访问该类的私有成员函数。
4》同一类的不同对象之间
同一个类的不同对象之间可以互相访问对方的私有成员函数(通过成员函数调用)。
2>利用上述2》解决
如下图,foo是私有成员函数,在类的外部不能被访问。
解决方案:通过上述2》使用友元函数来调用私有成员函数
更多推荐
所有评论(0)