c++11并发与多线程视频课程_哔哩哔哩_bilibili

上面这个视频对应的笔记链接如下:

C++11并发与多线程_胡胡浩特的博客-CSDN博客

1.线程库的基本使用_哔哩哔哩_bilibili

上面这个视频对应文档链接如下(关注up主微信,在微信公众号上打开的):

C++11 线程池多线程编程

一、并发、进程、线程的基本概念和综述

先看下面的笔记链接

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.线程库的基本使用_哔哩哔哩_bilibili

1、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>异常传递问题

五、线程传参详解

2.线程函数中的数据未定义错误_哔哩哔哩_bilibili

2、C++11 线程函数中的数据未定义错误

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() 保证了安全,但这种模式仍然被认为是脆弱的、容易出错的,原因如下:

  1. 容易被破坏:只要有人修改代码,比如把 t.join(); 注释掉,或者在 join() 之前不小心让 main 函数提前返回,问题就立刻暴露。
  2. std::thread 的析构要求:如果一个 std::thread 对象在未被 join() 或 detach() 的情况下被销毁(比如离开作用域),程序会调用 std::terminate() 直接终止,这是非常严重的错误。join() 是避免这种情况的必要手段,但依赖它来保证数据生命周期是一种“把安全建立在纪律上”的做法,不够健壮。
  3. 代码意图不清晰:传递一个指向栈变量的指针,会让其他阅读代码的人立刻警觉,因为它看起来就很危险。使用值传递或智能指针能让意图更清晰、更安全。

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》使用友元函数来调用私有成员函数

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐