Linux 线程

线程

1. 前言

在 Linux 中,一个进程涉及到各种大量的数据结构,当发生进程切换的时候,操作系统要做的工作非常多,比如保存并切换上下文数据,切换进程的虚拟地址空间,页表…,同时创建和销毁进程也是一个不小的性能开销,而且由于需要保证进程的独立性,带来的问题就是进程间通信效率并不高。

于是就引入了线程技术,故事不讲了

2. 线程

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。(来源:百度百科)

反正关键就是:一个进程包括多个线程,以及,线程可以执行任务,还可以参与调度,在 Linux 中,下面瞅瞅这些能力是怎么表现出来的

目前,在Linux 中,描述进程的结构体叫 task_struct,为了简化,描述线程的结构体也叫 task_struct,即线程复用了进程的代码。

  • 不同于 Windows,Windows 专门设计了描述线程的结构体,设计了各种线程需要的数据结构和算法,非常复杂

① 一个进程可以包括若干个线程 —— 对于同一个进程中的线程,这些线程共享进程的数据结构,比如虚拟地址空间,页表,文件描述符表…

进程包含一个或者多个 task_struct,还包括属于进程的各种数据结构,而这些 task_struct,是共用这些数据结构的。一个线程的创建和销毁不会让这些共享资源消失,而进程的销毁会带走这些数据结构以及线程,所以线程的创建和销毁是轻量级的

② 线程可以执行任务,并参与调度
如果没有线程,那么参与调度的就是进程了,我们理解这种进程为:只有一个线程的进程。
而现在一个进程中有多个线程,这些线程都可以独立作为一个执行流来让 CPU 进行调度,现在 Linux 中最小的调度单位就是线程了

在这里插入图片描述

3. 轻量化进程

所以,进程就有点像一个外壳 / 工具人了

  • 线程被设计出来后,进程在操作系统中的角色就更偏向于申请资源的角色了,当进程创建的时候,总会分配一大堆内核数据结构,对于一个进程中的线程来说,这些资源都是共享的,直接用就好了
  • 而线程作为一个个task_struct,被称为执行调度的基本单位,对于同一个进程中的线程来说,线程的切换开销对于进程来说是很小的,基本上恢复这个线程的 PCB 上下文数据就完成了大部分切换工作,不像进程

所以说,Linux 中认为线程是轻量级的进程,线程复用了进程的数据结构,进程负责申请一大片空间,线程作为进程的一个个执行流参与调度,线程的切换开销很小。

并且线程之间共享进程的资源,操作系统并不需要单独为这些线程分配和管理资源,线程的创建和销毁都不影响进程的共享资源,虽然线程也有自己独立的数据结构,但是这些数据结构相对来说都比较小,比如后面要说的线程独立栈

  • 如果从一个线程切换到另一个进程中的线程,这相当于进程切换了,那么还是会有很大的开销,因为需要进行进程各种数据结构的切换,所以轻量级是对同一个进程中的线程来说的,不是所有线程

总结一下

  • 线程的轻量化是对同一个进程中的所有线程来说的,这些线程共享进程的资源,所以他们在创建和销毁的时候都不需要再为这些共享资源重新分配空间和初始化操作
  • 创建和销毁的时候涉及到的数据结构都比较小巧,比如线程栈,task_struct,寄存器中的相关数据,不需要大规模的数据拷贝,所以开销也就比较小

4. 线程栈

(线程的实现逻辑和代码,在 Linux 中并没有完全写完,还有一部分代码是在 pthread 库中的,即libpthread.so动态库)

一个程序要运行起来,必须得有栈结构,像进程,或者说进程中的 main 线程,它在运行过程中依赖的栈结构是虚拟地址空间中的栈,但是该进程中的其他线程用的栈就不是这个了

每个线程都有一个自己独立的栈,方便自己运行起来,其他线程中的栈结构是 pthread 库中提供的

5. 进程和线程

然后就是那个经典的问题,进程和线程之间的区别:

  • 进程是资源分配的基本单位
  • 线程是执行调度的基本单位
  • 进程和进程之间是独立的,每个进程都用自己的虚拟地址空间,页表等数据结构,各自进程在自己的虚拟地址空间上运行,所以当其他进程崩溃,异常的时候,通常不会影响到其他进程
  • 同一个进程中的线程共享进程的大部分资源,所以一个线程出现异常或者崩溃,会波及到其他线程,甚至导致进程退出
  • 线程也有自己私有的数据,比如线程 ID,调度优先级,一组寄存器(用来保存上下文数据,用于调度),线程独立栈