「C++ 多线程」并发与多线程
文章大图来源: pixiv_id=122609228
1 进程与线程
1.1 进程
进程(Process)是计算机中程序关于某数据集合上的一次运行活动,是 系统进行资源分配和调度的基本单位。可以把进程看作是一个正在运行的程序的实例。例如我打开了 Edge 浏览器,操作系统会为其创建一个进程,进程中包含了运行程序的代码、所使用的数据、以及内存空间等各种系统资源。
从微观上来看,一个传统单核 CPU 在某一个特定时刻只能处理一个进程。
1.2 线程
线程是进程中的一个小的执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程 共享进程的资源(如代码段、数据段、文件资源等)
1.3 进程与线程的区别
1.3.1 资源分配
-
进程是资源分配的基本单位。每个进程拥有独立的内存空间、文件等资源。一个进程无法直接访问其他进程的资源,此时需要借助进程通信。
-
线程是共享进程资源的执行单位。线程除了拥有自己独立的栈空间、程序计数器等少量资源外,还共享进程的大部分资源。线程之间可以方便地共享数据。
1.3.2 调度开销
-
进程的调度开销较大。需要切换整个地址空间,保存和恢复大量的寄存器和其他上下文信息。
-
线程的调度开销较小。由于线程共享进程的地址空间,线程切换时只需要保存和恢复少量的寄存器和栈指针等信息。
1.3.3 独立性和健壮性
-
进程具有较高的独立性。一个进程的崩溃通常不会影响其他进程的运行,比较安全。
-
线程的独立性相对较弱。由于线程共享进程的资源,如果一个线程出现错误(如访问非法内存地址),可能会导致整个进程崩溃。
2 什么是并发
2.1 并发的含义
并发(Concurrency)是指计算机系统中多个计算或处理过程同时进行的能力。并发现象在我们日常生活中也很常见,我可以边听东方同人音乐边写博客,也可以边上课边看篮球比赛(
2.2 并发的方式
2.2.1 多核机的并发执行
在多核机上,每个核心都可以独立地执行不同的线程或进程,从而真正地同时处理多个任务,实现真正的 并行。
2.2.2 单核机的任务切换
单核机上往往通过 时间分片 和 上下文切换 实现任务的切换,每个任务被分配一个小的时间段执行,任务执行结束后,会从当前任务切换到下一个任务。可以实现宏观上的同时处理任务。
现在大部分的电脑 CPU 都是多核的,但即使在多核环境中,当线程数超过核心数时(电脑都会有线程限制数量,有时会大于核心数量,一切以实际测试结果为准),也需要在每个单核上进行时间片轮转和上下文切换。此时两种并发的方式是混合进行的。
2.3 并发的途径
2.3.1 多进程并发
运用独立的进程实现并发。
2.3.2 一个进程内多线程并发
同一个进程内的所有线程共享进程的大部分资源,但是这种方式往往比较复杂。
3 并发的意义
并发技术通常能够 提高资源利用率、增强系统的响应能力、支持复杂任务的分解与处理,适用于多用户交互、计算较为密集的任务等场景。
当然,有时候也不适合使用并发技术。当对于 一些简单的、顺序依赖很强的 程序,或者 对执行顺序有严格要求 的任务,并发可能会引入不必要的复杂性反而造成多余的开销,此时就不如使用传统的串行编程方式了。
4 简单C++多线程程序
我们有一个非常简单的单线程C++程序:
1 |
|
可以将其改写多线程程序:
1 |
|
每个线程需要一个 线程接口函数,例如上面代码中的 foo()
。实际上,主函数也是一个线程,我们可以看作是主线程。为了输出 Hello, this is MarisaMagic.
,我们启动了一个新的线程 t
(通过 std::thread
创建)来执行输出,此时该程序就有两个线程存在,一个是主线程从 main()
开始执行,另一个就是新的子线程从 foo()
开始执行。
当新线程启动之后,主线程也在同步执行。主线程有可能执行地比较快,如果不等待子线程执行完,会直接执行到 main()
结束,可能会在子线程 foo()
还未执行结束就结束整个程序。因此,我们需要调用 join()
使得主线程等待子线程。
这是个很简答的程序,实际上使用多线程是比较多余的。但是后面会遇到很多比较经典、复杂的程序。具体的基本 C++ 多线程(std::thread)工具的用法,进阶的并发程序以及高级线程管理等知识都会在后续慢慢更新 DA⭐ZE ~