Files
Operating-System/Thread/blocking.md
2026-03-22 17:40:44 +08:00

3.4 KiB
Raw Permalink Blame History

“线程阻塞”Thread Blocking是多线程编程中一个核心的运行状态。简单来说它是指线程因为等待某个事件或资源,暂时放弃 CPU 使用权,进入“不响应”状态的过程。


1. 什么是线程阻塞?

在操作系统的状态机中,线程通常经历 运行 (Running) -> 阻塞 (Blocked/Waiting) -> 就绪 (Ready) 的循环。

  • 直观比喻:就像你在银行排队取钱。如果柜台告诉你“系统断网了,请稍等”,你不能继续取钱(运行),也不能离开队伍(退出),只能在原地坐下等网络恢复(阻塞)。
  • 内核视角:内核将该线程从 “就绪队列”Ready Queue中移除放入 “等待队列”Wait Queue。此时 CPU 不再给它分配时间片。

2. 阻塞是如何发生的?(典型场景)

阻塞通常由以下几种情况触发:

A. I/O 阻塞 (最常见)

当线程请求磁盘文件、网络数据或键盘输入时CPU 的速度远高于硬件响应速度。

  • 过程:线程发起 read() 系统调用 -> 内核发现数据没准备好 -> 将线程设为阻塞态 -> 硬件完成读取后发出中断信号 -> 内核将线程唤醒。

B. 锁竞争 (Synchronization)

当线程尝试获取一个已经被其他线程占用的互斥锁Mutex时。

  • 过程:线程执行 lock() -> 发现锁被占用 -> 线程挂起(放入该锁的等待队列) -> 占有锁的线程执行 unlock() -> 内核挑选一个阻塞线程恢复运行。

C. 休眠与等待 (Sleep/Wait)

线程主动要求“停一会儿”。

  • 过程:调用 sleep()wait()。内核设置一个定时器或监听特定信号,到期后再将其唤醒。

3. 阻塞过程的底层原理

在 Linux内核级线程阻塞的进行涉及上下文切换 (Context Switch)

  1. 陷入内核:线程通过系统调用(如 read)进入内核态。
  2. 状态变更:内核将该线程对应的 task_struct(任务描述符)状态从 TASK_RUNNING 改为 TASK_INTERRUPTIBLE(可中断阻塞)或 TASK_UNINTERRUPTIBLE(不可中断阻塞)。
  3. 保存现场内核将当前线程的寄存器、程序计数器PC、栈指针等信息保存到该线程的内核栈中。
  4. 重新调度调度器Scheduler从就绪队列中挑选下一个线程把它的上下文加载到 CPU 寄存器中开始执行。

4. 阻塞 vs. 非阻塞 (Non-blocking)

这是开发者经常优化的方向:

  • 阻塞式:线程死等,代码写起来简单(顺序执行),但浪费线程资源。
  • 非阻塞式:线程调用后立即返回。如果数据没准备好,返回一个错误码(如 EAGAIN。线程可以先去做别的事过会儿再来轮询Polling
  • 多路复用 (epoll):这是你作为开发者可能会接触到的高级货——一个线程同时监控多个可能阻塞的 I/O谁好了就处理谁。

区分“阻塞”与“挂起”

  • 阻塞 (Block):通常是被动的,等待资源,内存不释放
  • 挂起 (Suspend):通常是主动的(或系统压力大),整个线程可能被交换到磁盘Swap以腾出内存空间。

你想看看在 C 语言或者 Rust 中,如何通过具体的代码调用来观察线程从运行态进入阻塞态的过程吗?