%% # 纲要 > 主干纲要、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)