banner
NEWS LETTER

第四章:线程

Scroll down

1. 线程的基本概念

什么是线程?

  • 定义:线程是进程中的一个执行流,是CPU调度和执行的基本单位。
  • 进程与线程的关系
    • 进程是资源分配的单位(拥有代码、数据、文件等资源)。
    • 线程是进程内部的一个实体,它共享进程的所有资源,但拥有自己独立的执行轨迹(程序计数器、寄存器集合、栈)。

单线程 vs. 多线程进程

  • 单线程进程:传统进程,只有一个执行流。
    • 代码 + 数据 + 文件 + 1组(寄存器、栈)
  • 多线程进程:一个进程内包含多个执行流。
    • 代码 + 数据 + 文件 + N组(寄存器、栈)

经典比喻
* 进程就像一个厨房,它拥有所有的资源:空间、水、电、厨具(对应代码、数据、文件等资源)。
* 单线程进程是厨房里只有一个厨师,他负责所有工作。
* 多线程进程是厨房里有多个厨师(线程),他们共享厨房的所有资源,但各自进行不同的任务(比如一个切菜,一个炒菜),协同完成一顿饭。


2. 使用多线程的好处

  1. 响应性:即使在单核CPU上,一个线程在等待I/O时,另一个线程可以继续执行,保持用户界面的响应。
    • 例子:在字处理软件中,一个线程处理用户输入,另一个线程在后台进行拼写检查,你不会因为检查拼写而无法打字。
  2. 资源共享:线程默认共享它们所属进程的内存和资源。创建和切换线程比创建进程的代价小得多。
  3. 经济性:创建和上下文切换线程比进程更快,消耗资源更少。
  4. 可伸缩性:在多处理器体系结构上,多线程可以真正并行运行在不同的CPU核上,从而显著提高性能。

3. 用户线程与内核线程

这是理解线程实现的关键。

用户线程

  • 管理方:在用户态线程库(如Pthreads, Win32 threads, Java threads)进行管理,内核并不知道它们的存在
  • 优点
    • 创建、管理、切换速度极快,因为不需要进入内核态。
  • 缺点
    • 如果一个用户线程执行了阻塞式系统调用,整个进程都会被阻塞,因为内核以为这个进程只有一个执行流。
    • 无法利用多核CPU实现真正的并行。

内核线程

  • 管理方:由操作系统内核直接支持和管理。
  • 优点
    • 当一个线程阻塞时,内核可以调度该进程内的另一个就绪线程或其他进程的线程。
    • 可以在多处理器上真正并行执行。
  • 缺点
    • 线程的创建、管理和切换都需要系统调用,代价比用户线程大。

4. 多线程模型

用户线程和内核线程需要通过某种方式关联起来,这就产生了三种模型。

1. 多对一模型

  • 映射多个用户线程映射到一个内核线程
  • 特点
    • 线程管理在用户空间进行,效率高。
    • 致命缺点:一个用户线程的阻塞系统调用会导致整个进程阻塞。无法在多处理器上并行。
  • 现状:基本已被淘汰。

2. 一对一模型

  • 映射一个用户线程映射到一个内核线程
  • 特点
    • 当一个线程阻塞时,其他线程可以继续运行。
    • 允许在多个处理器上并行执行多个线程。
    • 缺点:创建用户线程就需要创建内核线程,开销较大,可能会限制系统支持的线程数量。
  • 例子WindowsLinux

3. 多对多模型

  • 映射多个用户线程(数量不限)映射到数量相当或更少的内核线程上。
  • 特点
    • 克服了多对一模型并发度低的缺点,也克服了一对一模型开销大的缺点。
    • 内核可以创建足够数量的内核线程以供利用CPU,同时用户也可以创建任意数量的用户线程。
  • 变种:两级模型:是多对多模型的一种变体,允许一个用户线程绑定到一个特定的内核线程上(为某些重要线程提供保障)。

5. 线程相关的议题

  • fork()exec() 的语义
    • 当一个多线程进程调用 fork() 时,是只复制调用 fork() 的线程,还是复制所有线程?不同的系统有不同的实现。
    • 如果 fork() 之后立即调用 exec(),则复制所有线程是浪费的,因为 exec() 会用新程序覆盖整个地址空间。
  • 线程取消
    • 异步取消:一个线程立即终止目标线程。
    • 延迟取消:目标线程周期性地检查是否应该终止,允许它进行必要的清理工作(更安全)。
  • 信号处理
    • 信号是发给进程的还是某个特定线程的?
    • 多线程环境中,信号的处理非常复杂。
  • 线程池
    • 思想:在进程启动时创建一定数量的线程,放入“池”中待命。当有任务到来时,从池中唤醒一个线程来执行,执行完毕后返回池中。
    • 优点
      • 比需要时再创建新线程更快。
      • 限制了系统中并发线程的总数,防止资源耗尽。
  • 线程特定数据
    • 允许每个线程拥有属于自己的数据副本,尽管代码是共享的。
    • 例子:全局变量 errno。在多线程中,如果 errno 是真正的全局变量,一个线程可能会读取到另一个线程设置的错误码。使用线程特定数据,每个线程都有自己的 errno 副本。

6. 不同系统中的线程实现

Pthreads

  • 一个POSIX标准的线程API,定义了线程操作的行为规范,而不是具体实现。
  • 可以在用户级或内核级实现,常见于UNIX-like系统(Solaris, Linux, Mac OS X)。

Windows线程

  • 采用一对一模型
  • 每个线程包含:
    • 线程ID
    • 寄存器组
    • 独立的用户栈和内核栈
    • 私有存储区域
  • 以上这些统称为线程的上下文

Linux线程

  • Linux将线程视为一种能共享资源的特殊进程,称之为任务
  • 使用 clone() 系统调用创建线程,通过传递不同的参数标志,可以控制子任务与父任务共享哪些资源(如地址空间、文件描述符等)。

Java线程

  • Java线程是JVM的一部分,通常通过一对一模型映射到底层操作系统的原生线程。
  • 创建方式
    1. 继承 Thread 类。
    2. 实现 Runnable 接口(更推荐,因为Java不支持多继承)。
  • 线程状态
    • 新建:被创建,尚未启动。
    • 可运行:可能在运行,也可能在等待CPU。
    • 阻塞:等待一个监视器锁(同步)。
    • 等待:无限期等待另一个线程执行特定动作。
    • 超时等待:在指定时间内等待。
    • 终止:线程已执行完毕。

如果您喜欢我的文章,可以考虑打赏以支持我继续创作.

其他文章
目录导航 置顶
  1. 1. 1. 线程的基本概念
    1. 1.1. 什么是线程?
    2. 1.2. 单线程 vs. 多线程进程
  2. 2. 2. 使用多线程的好处
  3. 3. 3. 用户线程与内核线程
    1. 3.1. 用户线程
    2. 3.2. 内核线程
  4. 4. 4. 多线程模型
    1. 4.1. 1. 多对一模型
    2. 4.2. 2. 一对一模型
    3. 4.3. 3. 多对多模型
  5. 5. 5. 线程相关的议题
  6. 6. 6. 不同系统中的线程实现
    1. 6.1. Pthreads
    2. 6.2. Windows线程
    3. 6.3. Linux线程
    4. 6.4. Java线程
请输入关键词进行搜索