文章目录
🌈 一、线程的概念
⭐ 1. 线程是什么
-
线程是 CPU 调度的基本单位。
-
一个程序中的一条执行路线被称作线程 thread,确切的说,线程是一个进程内部的控制序列。
-
只要是进程,则至少会有一个执行线程。
-
线程是在进程的内部运行的执行流,本质是在进程地址空间内运行。
-
在 Linux 中,没有线程的概念,只有轻量级进程,其复用了进程 PCB 的结构和调度算法,但看到的 PCB 要比传统进程更轻量化。
- 由于线程的大部分内容和进程类似,如果要重新设计线程相关的结构体和调度算法就很不划算。
-
透过进程虚拟地址空间,能够看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
1. 进程的创建与映射
- 创建进程需要创建:进程控制块 task_struct、进程地址空间 mm_struct、页表。
- 虚拟地址 (进程地址空间) 和物理地址之间还要通过页表建立映射。
- 每个进程都拥有独立的地址空间和页表,即进程在运行时本身就具有独立性。
2. 线程的创建与映射
- 如果创建多个 task_struct,并让这多个 task_struct 共享进程地址空间和页表,这多个 task_struct 就是进程内部的多个执行流,即为 “线程”。
- 相比于进程,线程的创建成本明显低了很多,只需要创建出 task_struct 然后复用进程的地址空间和页表即可。
- 线程在进程内部运行,本质就是线程在进程地址空间内运行,即进程曾经申请的所有资源,几乎都是被所有线程共享的。
3. CPU 无需识别调度的是进程还是线程
- CPU 只关心执行流,一个进程内至少要有一个执行流,且不管进程内有多少个执行流,CPU 都是以 task_struct 为单位进行调度。
- 单执行流被 CPU 调度:
- 多执行流被 CPU 调度:
4. Linux 中线程的实现方案
- Linux 中并不存在真正的线程,只有轻量级进程的概念,其复用了进程的数据结构和相关算法。
- OS 中会存在着大量的进程,而一个进程中又存在 1 ~ n 个线程,线程的数量肯定是比进程多的。
- 如果 OS 要实现真正的线程,就要专门实现线程的结构体、创建线程、终止线程、调度线程等一切与线程有关的算法,越复杂越不好。
- 在 Linux 看来,进程控制块和线程控制块的相似点有很多,没必要专门去为线程设计一套数据结构,直接复用进程结构相关算法即可。
⭐ 2. 线程的优点
- 创建一个线程的代价比创建一个进程的代价小很多。
- 和进程间的切换相比,线程之间的切换需要 OS 做的工作会少很多。
- 线程占用的资源比进程少很多。
- 线程可以充分利用多处理器的可并行数量,一般 CPU 是几核的就创建几个线程。
- 在等待慢速 IO 操作的过程中,程序还可以执行其他的计算任务。
- 计算密集型应用,为了能在多处理器系统上运行,会将计算分解到多个线程中实现。
- IO 密集型应用,为了提高性能,将 IO 操作重叠,线程能同时等待不同的 IO 操作。
⭐ 3. 线程的缺点
- 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说,线程之间是缺乏保护的。
- 缺乏访问控制: 进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响。
- 编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。
⭐ 4. 线程的异常
- 线程崩溃会导致进程崩溃:单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
- 线程异常会导致进程终止:线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。
⭐ 5. 线程的用途
- 合理的使用多线程,能提高 CPU 密集型程序的执行效率。
- 合理的使用多线程,能提高 IO 密集型程序的用户体验。
🌈 二、进程和线程
⭐ 1. 进程和线程的区别
- 进程是资源分配的基本单位;线程是CPU调度的基本单位。
- 线程共享进程的数据,但也拥有自己的一部分数据:线程 ID、一组寄存器、独立栈结构、errno、信号屏蔽字、调度优先级。
⭐ 2. 进程的多线程共享
- 由于使用的是同一个地址空间,因此所谓的代码段、数据段都是共享的:
- 如果定义一个函数,在各线程中都可以调用。
- 如果定义一个全局变量,在各线程中都可以访问到。
线程共享的进程资源和环境
- 文件描述符表,进程打开一个文件后,其他线程也能够看到。
- 每种信号的处理方式,SIG_IGN、SIG_DFL或者自定义的信号处理函数。
- 当前工作目录。
- 用户 ID 和组 ID。
⭐ 3. 进程和线程的关系
- 在接触多线程之前,之前所接触的都属于单线程进程。
FL或者自定义的信号处理函数。
- 当前工作目录。
- 用户 ID 和组 ID。
⭐ 4. 线程私有的资源 (重要:面试)
- 线程的硬件上下文 (CPU 寄存器的值,强调线程的调度)
- 线程的独立栈结构 (强调线程的常规运行)