%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 进程与线程有什么区别?
| 特性 | 进程(Process) | 线程(Thread) |
| ---- | ----------------------- | -------------------------------------------------------------------- |
| | 操作系统**资源分配**的基本单位 | 操作系统**调度和执行**的基本单位 |
| 资源共享 | 不同进程彼此隔离,不共享资源 | 同一进程内的所有线程**共享进程资源** |
| 切换开销 | 进程间切换开销大,涉及**上下文保存/加载** | 线程间切换开销小,由于资源共享(例如共享进程的页表,所以不需要切换页表)故所需切换的资源少,<br>只需切换线程的私有数据、寄存器值等。 |
| 通信 | 进程间通信需要复杂机制 | **线程之间可以直接通过共享的内存空间、文件资源进行通信和数据传递** |
> [!summary]
>
> - "进程" 是操作系统进行**资源分配**的基本单位,而 "线程" 是操作系统**进行调度**的**基本单位**。
> - 单线程进程中,实际上具有一个 "主线程"。资源分配给进程后,**同一进程的所有线程共享该进程的所有资源**。
> - 所谓操作系统的任务调度,实际上的**调度对象是线程**,而**进程只是给线程提供了虚拟内存、全局变量等资源**。
![[02-开发笔记/05-操作系统/并发相关/POSIX 线程&同步 API#^1o2a91]]
<br><br><br>
# 什么是线程?
线程(Thread)是**进程中的 "一个独立的执行单元"**,是**进程中的实际运作单位**,也是**操作系统能够进行==运算调度的最小单位==**。
**同一进程中的线程**具有以下特点:
- **共享进程的代码段、数据段**
- **==共享进程资源==**:**同一进程内的所有线程共享相同的虚拟地址空间、全局变量、堆内存和文件描述符等资源,使线程可更容易地通信和数据共享**。
- **==独立执行==**:**每个线程有自己的栈、程序计数器和寄存器**,可以**独立执行**,且在调度上可以被 "**==并发执行==**"。
- **==支持多线程并发==**:同一进程的多个线程可以**并发执行**,在多核 CPU上还可以利用多核 "**并行**" 执行。
> [!caution] 线程不能独立于进程存在
>
> 线程是**所属进程的执行单元**,当进程退出时,**操作系统会清理与该进程相关的所有资源,包括线程**。
> 因此,线程不能独立于进程存在,当进程正常退出或意外崩溃时,**所有未完成的线程都会被强制终止**。
> [!info] 线程 ID 仅在其所属进程的上下文中有意义
>
> 进程 ID 在整个系统中唯一,而线程 ID 用于**在其所属进程中标识该进程的各线程**。
> [!NOTE] 同一进程中的多线程示意图:
>
> ![[_attachment/02-开发笔记/05-操作系统/并发相关/线程基础概念.assets/IMG-线程基础概念-217F552A49A08DA2F1374A040D314727.png|441]]
>
>
<br>
# 线程控制块 TCB
类似于**进程控制块 PCB**,**每个线程的状态信息** 保存在称为 "**==线程控制块==**"(Thread Control Block,**==TCB==**)的**结构体中**。
<br>
# 线程的上下文切换
**每个线程有自己的==栈==、==程序计数器 PC==、==寄存器==**,**在同一个 CPU 上==从一个线程切换到另一个线程==** 时,将发生 "**线程的上下文切换**" 。
线程的上下文切换会**将线程信息保存在 ==TCB== 的 `context` 中**(类似于 "**进程间切换** 时将上下文信息保存在**进程控制块 PCB** 的 `context` 结构体中,)
由于同一进程的线程间共享地址空间,因此 **不需要切换进程的页表**。
# 线程的栈
线程共享所属进程的地址空间,而**每个线程具有自己独立的栈**,
因此**线程中所有位于==栈上的变量、参数、返回值==和其他放在栈上的东西**,都被放置在称为 "**==线程本地==**"(**thread-local**)的地方。
![[_attachment/02-开发笔记/05-操作系统/并发相关/线程基础概念.assets/IMG-线程基础概念-EFC4EF4EAB307835685744459583D416.png|483]]
<br><br>
# 线程的类型
根据线程的 "实现方式",可分为**三种类型的线程**:
- **==用户级线程==(User-Level Threads)**:
- 定义:由**用户程序**管理的线程,**线程的创建、调度、管理都由用户态的线程库(如 POSIX 线程库)完成,不需要内核直接干预。**
- 特点:
- **内核不可见**:内核只可看到进程,不知道进程内部的多个用户线程的存在,因此**操作系统调度时只能==调度整个进程==到 CPU 上**,而**不能调度某个用户线程**。
- 优点:
- 创建&切换快、开销小。
- 缺点:
- 如果一个线程阻塞,整个进程都会阻塞。
- **不能利用多处理器进行真正的并行处理**,因为内核只能调度整个进程,对用户线程不可见。
- **==内核级线程==(Kernel-Level Threads)**:
- 定义:由**操作系统内核**管理的线程。**每个内核线程都==由内核创建、管理和调度==**,内核能够**直接调度这些线程**。
- 特点:
- **内核可见**:内核知道每个线程的存在,可以**单独调度某个线程**。
- **不阻塞其他线程**:如果一个内核线程被阻塞,内核可**继续调度同一进程内的其他线程**,不会阻塞整个进程。
- **可实现并行**:在多核机器上,内核**可将不同的线程调度到不同的 CPU 上**,实现真正的并行处理。
- 优点:
- **阻塞线程不会影响进程中的其他线程**,进程中的其他线程可以继续执行。
- 在多核机器上,可以实现真正的 "**并行**" 执行。
- 缺点:
- 内核线程的**创建、销毁和上下文切换开销较大**(涉及系统调用,并在内核空间中进行)
- **==轻量级进程==**(LightWeight Process,**LWP**):
- 定义:由**内核支持**的、介于用户线程和内核线程之间的抽象,通常用于**用户级线程库和内核之间的接口,允许多个用户线程映射到多个内核线程上**
- 特点:
- **用户线程与内核线程的结合**:每个 LWP 与一个内核线程关联,用户线程可以通过 LWP 与内核线程进行交互。用户线程可以映射到 LWP,而 LWP 则由内核进行调度。
- **阻塞处理**:当一个 LWP 阻塞时,内核可以调度与同一进程关联的其他 LWP,从而避免整个进程阻塞。
- **并行处理**:LWP 可以与多个处理器交互,支持真正的并行计算。
- 优点:
- 支持**多对多模型**:多个用户线程可以通过 LWP 映射到多个内核线程上,使得用户线程既可以拥有较低的创建和切换开销,又可以在内核层实现并行性。
- 在用户线程阻塞时,可以通过 LWP 避免进程整体阻塞,提供了更好的调度灵活性。
- 缺点:
- 实现复杂:由于 LWP 涉及到内核和用户空间之间的映射和调度,管理起来更加复杂。
- 增加了线程调度的开销:用户线程、LWP、内核线程三层架构的调度可能导致额外的开销。
#### 三者对比
| 特性 | 用户线程(User-Level Thread) | 内核线程(Kernel-Level Thread) | 轻量级线程(LWP, Lightweight Process) |
| --------- | ----------------------- | ------------------------- | ------------------------------- |
| **管理层次** | 用户空间管理 | 内核管理 | 用户线程与内核线程之间的接口 |
| **调度** | 由用户空间线程库调度 | 内核直接调度 | 用户空间线程库通过 LWP 调度,内核执行 |
| **内核可见性** | 不可见 | 可见 | 可见 |
| **开销** | 创建和切换开销小 | 创建和切换开销大 | 介于用户线程和内核线程之间 |
| **阻塞行为** | 一个线程阻塞会导致整个进程阻塞 | 一个线程阻塞不会影响其他线程 | 阻塞的 LWP 可以由内核调度其他 LWP |
| **并行性** | 无法实现真正并行(除非单个线程) | 支持真正的并行 | 支持真正的并行 |
| **适用场景** | 需要减少线程开销、用户控制调度 | 需要利用多核 CPU、阻塞操作较多的场景 | 支持多对多线程模型的操作系统场景 |
<br><br>
# 线程相关 FAQ
#### ❓<font color="#c00000">当一个线程崩溃时,其所属进程是否一定会崩溃?</font>
不一定,取决于进程如何处理接收到的 "信号" 或 "异常"。
当一个线程崩溃时(例如**触发段错误**或 "**抛出异常**"),OS 会**调用 `kill` 系统调用**,向进程发送 `SIGSEGV` 信号,
**若进程没有注册自己的信号处理函数,则会==由 OS 执行默认的信号处理程序==,让进程退出**。
- 在 C/C++ 程序中,进程可以注册自己的 "**信号处理函数**",并在其中调用 `pthread_exit(NULL)` 来**仅终止 "==当前线程=="**,而使进程&进程中其他线程继续运行。
- 在 C++程序中,若线程崩溃是由 "**未捕获的异常**" 导致的,则可以**通过 C++的异常处理机制进行捕获并处理**,防止整个进程崩溃。
- 在 java 中,线程崩溃不会导致 JVM 进程崩溃,也是因为 **JVM 自定义了信号处理函数**,**拦截了 SIGSEGV 信号**。
> [!caution] 示例:线程崩溃时自定义处理,防止进程崩溃。
>
> ```c
> #include <pthread.h>
> #include <stdio.h>
> #include <signal.h>
> #include <stdlib.h>
> #include <assert.h>
>
> void* thread_func1(void* arg) {
> char* p = "hello world";
> p[1] = 'H'; // 触发段错误, 非法内存访问
> return NULL;
> }
>
> void* thread_func2(void* arg) {
> printf("Done thread2\n");
> return NULL;
> }
>
>
> // 信号处理函数1: 调用exit退出进程
> void sigHandlerExitProcess(int sig) {
> printf("Signal %d catched! invoke`exit(sig)`\n", sig);
> exit(sig);
> }
>
> // 信号处理函数2: 调用pthread_exit(NULL), 仅终止当前线程, 而不影响整个进程&其他线程.
> void sigHandlerKillThread(int sig) {
> printf("Signal %d catched! invoke `pthread_exit(NULL)`\n", sig);
> pthread_exit(NULL); // 终止当前线程
> }
>
> int main() {
> // 注册信号处理函数
> // signal(SIGSEGV, sigHandlerExitProcess);
> signal(SIGSEGV, sigHandlerKillThread);
>
> pthread_t p1, p2;
> int rc;
> rc = pthread_create(&p1, NULL, thread_func1, NULL); assert(rc == 0);
> rc = pthread_create(&p2, NULL, thread_func2, NULL); assert(rc == 0);
>
> pthread_join(p1, NULL);
> pthread_join(p2, NULL);
> printf("Done main\n");
> return 0;
> }
> ```
>
>
<br>
#### ❓<font color="#c00000">一个进程可以创建多少个线程?与什么有关?</font>
参见[^1],和两个因素有关:
- (1)**==进程的虚拟内存空间上限==**
- **线程的栈空间,线程控制块等**位于**进程的虚拟内存中**,因此进程内存空间中除去 "**内核内存空间**" 后的 **"==用户内存空间==" 上限**会**制约最多可创建的线程数量**。
- Ps:Linux 下默认每个线程的栈空间为 `8MB`。
- (2)**==系统内核参数限制==**
- `/proc/sys/kernel/threadsmax`:表示**系统支持的最大线程数**(系统上的**总线程数**,而不是单个进程内的线程数)
- `/proc/sys/kernel/pid_max`:表示**系统全局的 PID 号数值的限制**,每一个进程或线程都有 ID,ID 的值超过这个数,进程或线程就会创建失败;
- `/proc/sys/vm/max_map_count`:表示系统的 "**最大虚拟内存映射数**"
- ![[_attachment/02-开发笔记/05-操作系统/并发相关/线程基础概念.assets/IMG-线程基础概念-194F1C636FBC5B8E7E36810D78864CE0.png|681]]
<br><br>
# 参考资料
# Footnotes
[^1]: [5.6 一个进程最多可以创建多少个线程? | 小林coding](https://xiaolincoding.com/os/4_process/create_thread_max.html)