Files
zhuangang/topics/cs/network-os/02-os-basics.md
T

91 lines
4.1 KiB
Markdown
Raw 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.
---
id: cs.network-os.os-basics
title: 操作系统基础:进程线程协程、I/O 模型与多路复用、内存与虚拟内存、文件描述符、死锁
type: lesson
domain: cs
stack: [network-os]
tags: [进程, 线程, 协程, GIL, I/O 多路复用, epoll, 虚拟内存, OOM, 文件描述符, 死锁]
level: basic
prerequisites: [cs.network-os.http-tcp]
est_hours: 3
related: [python.asyncio.index, devops.linux.troubleshooting, cs.network-os.interview]
week: [14, 16]
order: 2
---
# 操作系统基础:进程线程协程、I/O 模型与多路复用、内存与虚拟内存、文件描述符、死锁
## 解释说明
### 进程、线程、协程
| | 进程 | 线程 | 协程 |
| --- | --- | --- | --- |
| 资源 | 独立地址空间 | 共享进程的内存 | 共享线程 |
| 切换 | 内核,开销最大 | 内核,开销中等 | 用户态,开销最小 |
| 通信 | 管道、套接字、共享内存 | 共享变量 + 锁 | 共享变量(单线程内无需锁,但要注意 await 之间的状态变化) |
| 例子 | Gunicorn 多 worker | Java 线程池 | Go goroutine(多线程调度)、Python asyncio(单线程) |
**Python 的 GIL**:同一进程中同一时刻只有一个线程执行 Python 字节码;I/O 密集用线程或 asyncio 都可以,CPU 密集用多进程(Python 3.13 起有可选的无 GIL 自由线程构建,3.14 起官方支持,生态仍在适配,以官方文档为准)。
### I/O 模型
- 阻塞 I/O:一个连接占一个线程。
- **I/O 多路复用**(select / poll / **epoll**):一个线程监视大量文件描述符,内核告诉你哪些就绪。Nginx、Redis、Node.js、asyncio 都基于它。
- 异步 I/O(io_uring 等):提交请求,完成后通知。
### 内存
- **虚拟内存**:每个进程看到独立的地址空间,由页表映射到物理内存;缺页时从磁盘加载。
- 栈(函数调用、局部变量)和堆(动态分配)。
- **OOM**:内存不足时内核选择进程杀掉;容器超过内存上限被 OOMKilled(退出码 137)。
### 文件描述符
Linux 中一切皆文件:文件、套接字、管道都是文件描述符(fd)。每个进程有上限(`ulimit -n`),连接泄漏会导致 “Too many open files”。
### 死锁
四个必要条件:互斥、持有并等待、不可抢占、循环等待。预防:固定加锁顺序、加锁超时、减小锁粒度。数据库中的死锁同理(两个事务以相反顺序更新两行)。
## 案例
Python Agent 服务用 Uvicorn(asyncio):大量请求在等待模型返回(I/O),单个进程就能支撑很多并发连接;但如果在请求处理中同步调用了一个耗时的 CPU 操作(例如解析大 PDF),整个事件循环被阻塞,所有请求都卡住 —— 应该放到进程池或单独的任务队列。
## 代码示例
单线程 + `selectors`(Linux 上为 epoll)同时服务 50 个连接([multiplex_server.py](examples/tcp-basics/multiplex_server.py),运行通过):
```python
sel = selectors.DefaultSelector()
sel.register(srv, selectors.EVENT_READ, data=None)
while not stop.is_set():
for key, _ in sel.select(timeout=0.05):
if key.data is None: # 新连接
conn, _ = srv.accept()
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, data="client")
else: # 已有连接可读
data = key.fileobj.recv(1024)
...
```
```text
$ python multiplex_server.py
单线程同时服务 50 个连接,全部回显正确
```
## 易错点
- 在 asyncio 中调用阻塞函数(`time.sleep`、同步 HTTP 客户端、CPU 密集计算)。
- 认为多线程一定能提升 Python 的 CPU 密集任务性能。
- 连接、文件打开后不关闭,fd 耗尽。
- 容器内存上限设得过小,进程被 OOMKilled 却在应用日志中找不到原因。
## 面试常问
- 进程、线程、协程的区别?
- 什么是 I/O 多路复用?epoll 相比 select 好在哪里?
- 什么是死锁?怎么避免?
- Python 的 GIL 是什么?对你的服务有什么影响?