贝利信息

Golang并发与并行的区别解析

日期:2026-01-16 00:00 / 作者:P粉602998670
goroutine 默认实现并发而非并行,是否并行取决于 GOMAXPROCS 设置与 CPU 核心数;仅当 GOMAXPROCS > 1 且存在多 goroutine 可运行时,调度器才分派到多个 P 实现真正并行。

Go 语言里,goroutine 天生支持并发,但**默认不等于并行**——是否真正“同时执行”,取决于 GOMAXPROCS 设置和底层 OS 调度,不是写个 go f() 就自动多核跑满。

为什么 goroutine 是并发而非天然并行

Go 运行时用的是 M:N 调度模型(GPM),即大量 goroutine(G)被复用到少量系统线程(M)上,再由这些线程绑定到 P(逻辑处理器)运行。而 P 的数量默认 = CPU 核心数,但可通过 runtime.GOMAXPROCS(n) 修改。

关键点在于:即使你启动了 1000 个 goroutine,如果 GOMAXPROCS=1,它们依然在单个 P 上交替执行——这是典型的**并发(concurrency)**;只有当

GOMAXPROCS > 1 且有足够多可运行的 goroutine 时,调度器才可能把它们分派到多个 P 上,从而触发真正的**并行(parallelism)**。

如何确认你的程序实际发生了并行

不能只看 go topps 显示多个线程——Go 的 M 线程数可能远小于 goroutine 数,且很多是休眠态。要看真实 CPU 利用率分布和调度行为。

常见并发陷阱:闭包变量捕获 + 并行误判

很多人以为 for 循环里起 goroutine 就是“并行处理每个元素”,结果输出全是最后一个值——这不是并行问题,而是并发下的经典闭包陷阱。

for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i) // 全部打印 3
    }()
}

原因:所有 goroutine 共享同一个变量 i 的地址,循环结束时 i == 3,它们都读到了最终值。

什么时候该调大 GOMAXPROCS?

绝大多数 Go 服务(HTTP、RPC、DB 访问)完全不需要手动设置 GOMAXPROCS。默认值(等于物理核数)对 IO 密集型负载已足够。强行设高反而有害。

真正难的从来不是“怎么开并行”,而是判断“哪里需要并行”——IO 等待多的地方堆 goroutine 就行,CPU 瓶颈处才要拆任务+开多 P;更隐蔽的是,有些“CPU 密集”其实是内存带宽或 GC 压力造成的假象,盲目调高 GOMAXPROCS 只会让 GC 更喘不过气。