「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
2
3
4
5
6
7
#include <iostream>

int main(){
std::cout << "Hello, this is MarisaMagic." << "\n";

return 0;
}

可以将其改写多线程程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <thread>

void foo(){
std::cout << "Hello, this is MarisaMagic." << "\n";
}

int main(){ // 主线程从 main() 开始执行
std::thread t(foo); // 新的子线程 t 从 foo() 开始执行
t.join(); // 等待 t 线程执行完成

return 0;
}

每个线程需要一个 线程接口函数,例如上面代码中的 foo()。实际上,主函数也是一个线程,我们可以看作是主线程。为了输出 Hello, this is MarisaMagic. ,我们启动了一个新的线程 t(通过 std::thread 创建)来执行输出,此时该程序就有两个线程存在,一个是主线程从 main() 开始执行,另一个就是新的子线程从 foo() 开始执行。

当新线程启动之后,主线程也在同步执行。主线程有可能执行地比较快,如果不等待子线程执行完,会直接执行到 main() 结束,可能会在子线程 foo() 还未执行结束就结束整个程序。因此,我们需要调用 join() 使得主线程等待子线程。

这是个很简答的程序,实际上使用多线程是比较多余的。但是后面会遇到很多比较经典、复杂的程序。具体的基本 C++ 多线程(std::thread)工具的用法,进阶的并发程序以及高级线程管理等知识都会在后续慢慢更新 DA⭐ZE ~

参考

  1. 《C++并发编程实战》第1章 你好,C++的并发世界!

「C++ 多线程」并发与多线程
https://marisamagic.github.io/2024/12/17/20241217/
作者
MarisaMagic
发布于
2024年12月17日
许可协议