Goroutine基础
什么是 Goroutine?
Goroutine是Go语言中的轻量级线程,是Go并发编程的基础单元。通过go内置关键字可以创建一个新的Goroutine,例如:
这使得Go语言天生支持并发编程,而不需要像其他语言那样显式地创建线程。
Goroutine 的特性
- 轻量级:Goroutine 的栈空间大小只有 2KB,而线程的栈空间大小通常是 1MB 左右。
- 并发执行:多个 Goroutine 可以在一个线程中并发执行,充分利用多核处理器的性能。
- 调度器:Go 运行时内置了高效的调度器,负责自动化管理 Goroutine 的生命周期。相比于传统的内核级线程,Goroutine 的切换完全在用户态完成,无须陷入内核态,因此上下文切换的成本极低。
Go并发模型
Go 的并发模型深受 CSP(Communicating Sequential Processes)理论启发。在该模型中,程序被划分为多个独立的 Goroutine,它们之间通过 Channel 传递消息来协同工作。这种“通过通信来共享内存”的机制,从根本上规避了传统多线程模型中由于直接共享内存而产生的复杂锁竞争问题。
Go 语言有一句著名的格言:“不要通过共享内存来通信,而要通过通信来共享内存。” (Do not communicate by sharing memory; instead, share memory by communicating.) 这句话是理解 Go 并发模型的灵魂。
并发 VS 并行
并发(Concurrency):指在同一时间段内“处理”多个任务的能力。它侧重于任务的组织与调度,通过在任务间快速切换实现逻辑上的“同时”。 并行(Parallelism):指在同一时刻“执行”多个任务的能力。它侧重于利用多核硬件资源,实现物理上的真正“同时”。
Go 的并发模型通过 Goroutine 提供了优秀的任务组织方式。在多核 CPU 环境下,Go 调度器能够自动将多个 Goroutine 分发到不同的硬件核心上,从而将“逻辑上的并发”转化为“物理上的并行”。
在 Go 调度器的管理下,Goroutine 的执行是异步且非确定性的。基于 GMP 模型,Go 实现了多核间的物理并行与单核内的逻辑并发:它既能将任务分发到不同核心同时运行,也能在单个核心上通过极速切换处理海量任务,从而实现真正的高性能并发。
并行中的并发
为了更好地理解“每个核也能并发执行”,我们可以把 CPU 想象成一个高速运转的工厂:
- 多核并行(宏观):如果你的电脑有 8 个核,Go 调度器会尽量启动 8 个内核线程(M),并分配 8 个逻辑处理器(P)。此时,8 个核可以在同一时刻真正地执行 8 个不同的 Goroutine。
- 单核并发(微观):虽然每个核在某一瞬间只能运行一个 G,但每个核的“待办清单”(P 的本地队列)里可能有成百上千个 G。Go 调度器会在单个核上进行极速切换(通常是微秒级)。即便是在并行执行的过程中,每个核心内部依然在不停地进行并发切换。
Go 调度器(GMP)
GMP 是 Go 语言的线程模型,包含三个核心概念:内核线程(M)、协程(G)以及逻辑处理器(P)。

GMP
GMP 模型核心角色
- G (Goroutine):用户态轻量级线程,由 Go 运行时管理。存储了协程执行的代码、上下文(如寄存器、栈信息)以及运行状态。
- M (Machine):操作系统线程(内核线程),是代码运行的物理载体。M 必须绑定一个 P 才能执行 G。
- P (Processor):逻辑处理器。它承载了 G 运行所需的资源(如内存分配状态、本地队列等)。P 的数量由
GOMAXPROCS决定,代表了真正的并行度。
调度机制与资源分配
Go 调度器的职责是将所有处于 runnable 状态的 Goroutines 均匀分布到在 P 上运行的 M 中。它采用了 M:N 模型:即 M 个 Goroutine 被分配到 N 个内核线程上,而这些线程运行在最多 GOMAXPROCS 个逻辑处理器(P)上。
1. 队列体系
- 本地运行队列 (LRQ):每个 P 拥有一个私有的本地队列(上限 256 个)。新创建的 G 优先存入本地队列;若队列已满,则会将本地队列中一半的 G 移交给全局队列。
- 全局运行队列 (GRQ):存放所有 P 共享的等待运行的 G。访问全局队列需要加锁,开销相对较大。
2. 高效调度策略
- 工作窃取 (Work-stealing):当一个 P 发现自己的本地队列(LRQ)已经没有 G 时,会主动从其他 P 的队列中“偷”取一半的 G 来运行。这种“损人利己”的精神确保了 CPU 核心不会处于饥饿状态,最大化了系统吞吐量。
- 移交机制 (Hand-off):每个 M 必须依附于一个 P 才能运行。如果 M 上的某个 G 发生了阻塞操作(如系统调用),该 M 会释放 P,由 P 寻找或创建一个新的 M 来继续运行本地队列中剩余的 Goroutines,从而避免了阻塞导致的性能塌陷。
3. 总结分配原则
- 空间维度(并行):
GOMAXPROCS设置了 P(逻辑处理器)的数量。Go 调度器将 G 分配给 P,而操作系统(OS)负责将承载 P 的内核线程(M)分发到不同的物理 CPU 核心上。 - 时间维度(并发):在每个核心内部,通过“时间片轮转”和“异步抢占”让海量的 G 轮流使用该核心。
这种“多核分流 + 单核抢占”的组合拳,正是 Go 能够以极低资源消耗处理海量并发任务的核心秘诀。