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

59 lines
3.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
“线程阻塞”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 中,如何通过具体的代码调用来观察线程从运行态进入阻塞态的过程吗?