用生活比喻讲透:协程到底是什么?凭什么比线程轻量?
开场比喻:餐厅里的服务员 vs 后厨的专职厨师
你去一家网红餐厅吃饭,发现每个服务员同时照顾五六桌客人。这边点完菜,转头去给另一桌上菜,再回来收空盘子——一个人干了好几个人的活。
但后厨不一样:切菜的只切菜,炒菜的只炒菜,一个萝卜一个坑。要是让切菜师傅也去炒菜,那叫线程切换,成本高得要命。
Kotlin 协程,就是那个眼观六路、耳听八方的服务员。它可以在一个线程上“同时”处理很多任务,遇到需要等的事情(比如等菜出锅)就先去招呼别的客人,等菜好了再回来接着干。
一、线程是“专职员工”,协程是“临时工”
你想想,线程就像公司里的正式员工:每个人都有固定的工位(栈内存)、固定的办公设备(CPU时间片)。招聘一个线程成本不低,要是让1000个线程同时干活,公司得有一整栋楼才行。
协程就不一样了,它是临时工——不需要独立工位。几个协程可以挤在一个线程上,轮流用同一张桌子。线程说“我这个岗位只能一个人”,协程说“咱们排个队,谁有事谁先让一下”。
但注意:协程虽然是“临时工”,但它有完整的上下文保存机制——就像临时工也戴着自己的工牌(continuation),随时能被叫回来继续干刚才的活,不会丢失进度。
技术上就是:线程由操作系统调度,切换时需要保存寄存器、刷新CPU缓存,很重。协程由程序自己调度,切换就像调用一个普通函数,只需要保存几个变量,成本低到可以忽略。
所以你能开几万个协程,但开几千个线程手机就会卡成PPT。
二、挂起函数:“我去抽根烟,你先上”
协程最精妙的机制是挂起(suspend)。还是服务员例子:客人点完菜,服务员把单子递给后厨,然后不会傻站着等——而是挂起自己这个任务,去服务别的客人。等菜做好了,再回来继续上菜。
这就是非阻塞等待。换成线程呢?如果等网络请求返回,线程会真的卡在那里,什么事也干不了,白白浪费资源。
注意区分:挂起(suspend)是非阻塞的等待——线程去干别的协程了;阻塞(block)是线程真的卡死(比如在协程里调用Thread.sleep)。如果你在挂起函数里写阻塞代码,照样会堵死线程,协程的优势就没了。
代码里是这样的:
suspend fun fetchUser() : User {
// 发起网络请求,然后挂起自己,不阻塞线程
return withContext(Dispatchers.IO) { api.getUser() }
}
挂起时,协程会把自己的“现场”保存到一个很轻的对象里,线程可以转头去跑别的协程。等网络回来,协程再从那个点恢复执行。
调度器和结构化并发:协程不是绑死在一个线程上,Kotlin提供了Dispatchers.Main(UI线程)、Dispatchers.IO(网络/数据库)、Dispatchers.Default(CPU计算)。你可以自由切换,就像服务员可以去前厅、后厨、仓库。更妙的是,协程有父子关系,父协程取消时所有子协程自动取消,这叫结构化并发,避免资源泄漏。
说白了就是:遇到耗时操作,协程主动让出线程,让别人先干。不像线程那样独占线程不释放。
三、协程就是“带状态机的普通函数”
再深入一点点(放心,不难)。你可以把协程理解成一个能暂停、能恢复的函数。编译器会偷偷把你的挂起函数改造成一个状态机——每次挂起的地方是一个状态,恢复时看该执行哪一步。
这就像你看视频:暂停了,进度条记住了时间点,点继续就接着播。线程呢?线程是流水线传送带,卡住就整条线停;协程是带书签的多任务手册,翻到哪页读哪页。
所以协程不需要操作系统帮忙,自己就把调度玩明白了。它减少了线程间锁的竞争(因为大部分时间在同一个线程上跑),但协程间的共享状态仍需注意原子性。
总结表格:线程 vs 协程
| 对比项 | 线程 | 协程 |
|---|---|---|
| 调度者 | 操作系统 | 程序自己(Kotlin运行时) |
| 切换成本 | 高(系统调用、保存寄存器) | 极低(就像函数调用) |
| 并发数量 | 几千个就崩 | 几十万个轻轻松松 |
| 等待时的行为 | 阻塞(线程卡住) | 挂起(线程继续干别的) |
| 内存占用 | 每个线程约1MB栈 | 每个协程约几十字节 |
面试官爱怎么问?
问:协程比线程轻量,到底轻在哪?
答:线程的轻量是资源上的轻。线程切换要操作系统介入,保存一大堆CPU状态,内存也大。协程切换就是普通的函数调用,编译器帮你做了状态机,不需要内核参与。打个比方:线程像搬家,得请搬家公司;协程像换个座位,站起来走两步就行。
问:挂起函数和普通函数有什么区别?
答:普通函数一跑到底,中间不能停。挂起函数可以主动让出线程,等耗时操作完成后再回来继续跑。就像你去办事,排队时不是死等,而是先干别的,快排到你了再回来。注意:挂起是非阻塞的,线程不会卡死;如果你在挂起函数里写Thread.sleep(),那就变成阻塞了,协程的优势就没了。
三句话人话总结
- 资源层:协程内存占用极小(几十字节),一个线程能跑成千上万个协程,像纸飞机对比战斗机。
- 调度层:挂起是协作式让权,切换在用户态完成,遇到IO就主动让出线程,绝不阻塞。
- 适用层:协程适合IO密集型任务(网络、数据库),不适合纯计算密集型(排序、加密)——后者该用线程池。
汇总导航