Appearance
协程
- 是轻量级的执行单元,由 Go 运行时(而非操作系统)管理,是 Go 并发编程的核心特性。
系统运行单元
程序(program)
- 是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。
进程(process)
- 进程是操作系统资源分配的基本单位,是一个正在运行的程序实例,拥有独立的内存空间、文件描述符、寄存器状态等系统资源。
- 进程间相互独立,通信需要通过 IPC(如管道、信号、共享内存等),开销较大。
- 由操作系统内核调度,切换成本高(需保存 / 恢复整个进程的上下文)。
- 一个程序可以启动多个进程(如多进程服务),但资源消耗高。
线程(thread)
- 线程是进程内的执行单元,是操作系统调度的基本单位,共享所属进程的内存空间和资源,但有独立的栈和寄存器。
- 线程属于进程,一个进程可包含多个线程(多线程),线程间通信通过共享内存,比进程通信高效。
- 由操作系统内核调度(内核级线程),切换成本中等(需保存 / 恢复线程上下文,但共享进程资源)。
- 线程数量有限(受系统内存和内核限制),过多线程会导致调度效率下降。
协程(goroutine)
- 协程是一种用户态的轻量级线程,由 Go 运行时管理,比线程更轻量级,切换成本低。
GMP 调度模型

运行是调度
- 协程不由操作系统内核调度,而是由 Go 运行时的 GMP 调度模型 管理
G:Goroutine表示一个协程,是 Go 语言并发的基本单元。M:Machine表示操作系统内核线程(OS Thread),是真正执行代码的 “物理载体”。P:Processor表示 “逻辑处理器”,是连接 G 和 M 的中间层,负责管理 G 的调度队列。
调度流程
G的创建与入队
- 当使用
go关键字创建G时,G会被加入当前P的本地运行队列(LRQ)。若 LRQ 满(默认容量 256),则会被转移到全局运行队列(Global Run Queue, GRQ)。
M 绑定 P 执行 G
M 启动后,会从P的LRQ中获取G` 并执行。- 若
LRQ为空,P会先尝试从GRQ中 “偷” 一批G(通常是GRQ一半)到LRQ,再执行。 - 若
GRQ也为空,P会从其他P的LRQ中 “偷” 取G(负载均衡,通常偷取对方一半的G),避免自身M空闲。
G 的阻塞与唤醒
- 当
G执行阻塞操作(如 channel 通信、time.Sleep、锁等待、系统调用等):- 若阻塞在
Go运行时内部(如 channel 或 sync.Mutex),G会被标记为阻塞状态,从P的LRQ移除,M会继续从LRQ中获取其他G执行(此时G不会阻塞M)。 - 若阻塞在系统调用(如网络 I/O、文件读写),
M会暂时与P解绑,进入休眠状态;当系统调用完成后,G会被重新加入某个P的LRQ或GRQ,等待再次被调度。
- 若阻塞在
时间片轮转
- 为避免单个
G长期占用M,Go 调度器会为G设置时间片(默认约 10ms)。当时间片用完,G会被暂停,重新放入LRQ尾部,让其他G获得执行机会(实现公平调度)。
GOMAXPROCS
- GOMAXPROCS 控制 P 的数量,直接决定了程序的最大并行度(同一时间最多有 GOMAXPROCS 个 G 被执行)。
- 默认值为 CPU 核心数(可通过 runtime.NumCPU() 查看),通常无需修改。
