线程与协程
1. 线程与协程
1.1. “函数调用级别”的切换、上下文切换
1. 函数调用级别的切换
“函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。
举例说明:
当你在程序中写一个函数调用:
funcA()
然后 funcA
执行完后返回,CPU 不需要保存太多额外的信息,只是:
- 压栈/出栈(保存调用点、局部变量等)
- 跳转指令地址(比如函数返回到调用者)
这种切换就是非常轻量级的,发生在用户态,速度快、成本低。
协程之间的切换通常只涉及「函数调用级别」的保存和恢复,例如程序计数器(PC)、栈指针(SP)等。
2. 上下文切换
上下文切换是操作系统在多任务之间切换时保存和恢复执行状态的过程,通常发生在线程/进程之间。
典型上下文包含:
- CPU 寄存器(程序计数器 PC、堆栈指针 SP、通用寄存器)
- 内存映射 / 虚拟地址空间(尤其是进程间)
- 线程/进程控制块(如 PCB/TCB)
上下文切换的代价大:
- 保存当前线程的寄存器、状态
- 切换到内核态(Trap)
- 操作系统调度另一个线程/进程
- 恢复新线程的上下文
- 切换回用户态,继续执行
这会消耗 CPU 时间、内存带宽和系统资源,频繁切换会严重影响性能。
对比总结
项目 | 协程切换(函数调用级别) | 线程/进程切换(上下文切换) |
切换方式 | 用户态,像调用函数一样 | 操作系统调度,涉及内核态 |
状态保存/恢复 | 少量(程序计数器、栈指针) | 全部寄存器、堆栈、内存映射等 |
是否切内核态 | 否 | 是(陷入内核) |
开销 | 极小 | 较大(系统调用 + 状态切换) |
性能影响 | 轻微 | 明显(频繁切换容易降低性能) |
1.2. 线程(Thread)
概念:
线程是操作系统调度的最小单位,一个线程对应一条执行路径。多个线程可以并发执行任务,从而提升程序的响应性和性能。
特点:
- 每个线程有自己的堆栈空间和程序计数器;
- 多个线程共享进程内的资源(如内存、文件);
- 线程之间切换需要操作系统调度,代价较高(上下文切换);
- 在多核 CPU 下,线程可以实现真正的并行(每核跑一个线程);
- 适合 I/O 密集和 CPU 密集场景,但创建和调度较重。
举例(Python 中):
import threadingdef task():print("线程正在运行")t = threading.Thread(target=task)
t.start()
1.3. 协程(Coroutine)
概念:
协程是一种用户态的轻量级线程,又叫微线程,不由操作系统调度,而是由程序自己控制何时挂起、何时恢复。
特点:
- 本质是单线程内的并发;
- 用于I/O 密集型任务非常高效(如网络请求、文件读写);
- 切换开销小,不需要线程上下文切换;
- 依靠语言或框架调度(如 Python 的
asyncio
、Go 的 goroutine); - 不适合 CPU 密集任务(因为不能并行运算)。
举例(Python asyncio):
import asyncioasync def task():print("协程正在运行")await asyncio.sleep(1)print("协程运行结束")asyncio.run(task())
1.4. 线程 vs 协程对比总结
对比点 | 线程(Thread) | 协程(Coroutine) |
调度方式 | 操作系统调度 | 程序主动让出控制权(如 |
是否并发/并行 | 可实现并发,也可在多核并行 | 仅并发,不并行(除非多线程协程) |
切换开销 | 大(上下文切换、内核态) | 小(用户态) |
编程复杂度 | 中等(注意锁、共享数据) | 简单(适合异步 I/O) |
适用场景 | CPU 密集、I/O 密集 | 高并发 I/O 密集(如爬虫、Web服务) |
Python 举例 |
|
|
什么时候选线程?什么时候选协程?
场景 | 推荐使用 | 理由 |
文件读写、数据库操作 | 协程(asyncio) | I/O 密集、无需上下文切换、轻量高效 |
网络服务(如爬虫、Web) | 协程(asyncio) | 可处理成千上万连接,资源开销低 |
CPU 密集型计算 | 多线程 / 多进程 | 协程无优势,应考虑并行处理能力 |
简单并发 | 线程即可 | 写法直观,不涉及复杂 async/await |