进程、线程、协程,还在傻傻分不清楚?
引言
作为后端开发人员,理解及进程、线程与协程是理解现代高并发编程(比如 GO 语言、Java 21 的虚拟线程)的基石。
简介
简单来说,进程是操作系统分配资源的最小单位,线程是操作系统CPU调度的最小单位,而协程则是用户态级别的一种轻量级线程,它的调度不由操作系统控制,而是由应用程序自己控制。
详解
- 首先,我们来谈谈进程。进程是操作系统中进行资源分配和调度的基本单位,它拥有自己独立的内存空间和系统资源。每个进程都有独立的堆和栈,不与其他进程共享。进程间通信需要通过特定的机制,如管道、消息队列、信号量等。由于进程拥有独立的内存空间,因此其安全性和稳定性相对较高,但同时上下文切换的开销也大,因为需要保存和恢复整个进程的状态。
- 接下来是线程。线程是进程内的一个执行单元,也是 CPU 调度和分派的基本单位。与线程不同,进程共享进程的内存空间,包括堆和全局变量。线程之间通信更加高效,因为它们可以直接读写共享内存。线程的上下文切换开销较小,因为只需要保存和恢复线程的上下文,而不是整个进程的状态。然而,由于多个线程共享内存空间,因此存在线程安全和资源竞争问题,需要通过同步或互斥机制来解决。
- 最后是协程。协程是一种用户态的轻量级线程,其调度完全由用户程序控制,而不需要内核的参与。协程拥有自己的寄存器上下文和栈,但与其他协程共享堆内存。协程的切换开销非常小,因为只需要保存和恢复协程的上下文,而无需进行内核级的上下文切换。这使得协程在处理大量并发任务时具有非常高的效率。然而,协程需要程序员显示地进行调度和管理,相对于进程和线程来说,其编程模型更为复杂。
没有完全理解?这里我们重点从开销和切换机制来进行对比,帮助你更好地理解。
进程与线程的区别:
- 进程之间是相互隔离的,拥有独立的内存空间,一个进程挂了通常不会影响其他进程。
- 而线程是进程内部的执行单元,它们共享进程的堆内存和资源。所以线程间通信(如读写变量)通常很快,但如果一个线程发生严重错误,如 OOM,可能会导致整个进程崩溃。
另外,线程的创建和销毁虽然比进程轻,但依然需要操作系统内核介入,设计用户态和内核态的切换,开销并不低。
关于线程与协程的区别:
- 传统的线程(比如 Java 中的 Thread)通常是1:1映射到操作系统的内核线程上的。这意味着线程切换需要陷入内核态,保存寄存器、栈信息等,开销大概在毫秒级,且每个线程占用约 1MB 内存。
- 而协程(Coroutine)完全运行在用户态。它的切换不涉及内核态,只是一些寄存器和指针的保存,开销极小(纳秒级),且内存占用极低(几KB),这使得我们可以轻松在单机上运行成千上万个协程,非常适合高并发的 IO 密集型场景。
协程在 Java 生态中的应用
在 Java 生态中,我们早期主要使用 new Thread(),这是重量级的内核线程。为了解决高并发下的性能瓶颈,我们引入了 Netty 这种基于 Reactor 模式的异步框架,但它需要写回调,代码逻辑比较割裂。
而在最近的 Java 21 中引入了虚拟线程(Virtual Thread),这本质上就是 Java 官方实现的协程。他让我们能用传统同步阻塞的代码风格(比如直接写 socket.read()),底层却能享受异步非阻塞的高性能。这解决了传统线程模型在高并发下资源消耗过大的问题。
引申问题:为什么协程适合 IO 密集型,不适合 CPU 密集型?
答案就是协程的核心优势是利用等待时间。
- IO密集型(查库,请求网络):线程大部分时间在等。这时候协程可以在用户态快速切换任务,把 CPU 榨干,不让 CPU 闲着,效率极高。
- CPU 密集型(视频解码,加密计算):线程一直在全速计算,没有等待时间。这时候切换协程反而多了一道手续(虽然轻微),不如一直让线程算下去。而且协程本质是单线程的(在单线程内切换),无法利用多核优势,CPU 密集型还是得靠多线程并行。
总结
- 进程是抢地盘的。
- 线程是抢 CPU 的。
- 协程是聪明地利用“等待时间”来干私活的。






