go程序默认启用多核需显式设置gomaxprocs,否则仅用单核;检查用runtime.gomaxprocs(0),容器中需适配cgroups限制;goroutine数量不等于并行度,io密集靠调度器自动并发,cpu密集依赖gomaxprocs与任务拆分。

Go 程序默认就用多 CPU,但得显式启用
Go 运行时默认只使用 1 个 OS 线程(M)绑定 1 个逻辑处理器(P),也就是只跑在单核上——哪怕你机器有 32 核,runtime.GOMAXPROCS 默认值仍是 1(Go 1.5+ 后改为 runtime.NumCPU(),但某些嵌入式或容器环境仍可能被覆盖)。所以“没提速”往往不是代码问题,而是 GOMAXPROCS 没生效。
检查当前值:
fmt.Println(runtime.GOMAXPROCS(0))返回的是当前生效的 P 数。若为 1,说明并行能力被锁死了。
- 启动时设环境变量:
GOMAXPROCS=8 go run main.go - 代码里尽早调用(建议
main函数第一行):runtime.GOMAXPROCS(runtime.NumCPU()) - 注意:Docker 容器中
runtime.NumCPU()可能返回宿主机核数,需配合cgroups限制后读/sys/fs/cgroup/cpu/cpu.cfs_quota_us手动计算
goroutine 不等于并行,IO 密集和 CPU 密集要分开看
开 1000 个 goroutine 做 HTTP 请求,基本都能并发跑满多核(因为大部分时间在等网络 IO,P 会调度其他 goroutine);但开 1000 个 goroutine 死循环算斐波那契,实际只有 GOMAXPROCS 个在真·并行执行,其余都在就绪队列里等 P。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- CPU 密集型任务:必须确保每个 goroutine 工作量足够大,避免频繁抢占;可考虑用
runtime.LockOSThread()绑定到特定线程(慎用,易导致死锁) - IO 密集型任务:无需额外操作,Go 的 netpoller 天然支持高并发,重点应放在连接池、超时控制、错误重试
- 混合型任务:用
sync.Pool复用对象,减少 GC 压力——GC STW 会暂停所有 P,间接削弱并行效率
别让 channel 成为性能瓶颈
无缓冲 channel 的发送/接收是同步阻塞操作,底层涉及锁和调度器介入;大量 goroutine 往同一个 channel 写,会形成竞争热点,反而比单协程还慢。
- 写日志、聚合结果这类场景,优先用带缓冲 channel(如
make(chan int, 1024)),缓冲区大小按吞吐预估,避免频繁阻塞 - 需要扇出/扇入时,用多个 worker channel + select 分流,而不是单个中心 channel
- 确认是否真需要 channel:纯数据传递可用
sync.Map或atomic操作,延迟更低
pprof 要看对地方,别被 goroutine 数骗了
go tool pprof -http=:8080 cpu.pprof 显示的“flat”占比高,不代表它占满 CPU——可能只是调度器频繁切换上下文。真正要看的是 runtime.mcall、runtime.gopark 是否异常高,以及 syscall.Syscall 是否卡在某个系统调用上。
- 采样前加
runtime.SetMutexProfileFraction(1)和runtime.SetBlockProfileRate(1),暴露锁和阻塞问题 - 对比
top -H -p $(pgrep yourapp)中各线程的 CPU%,若某几个长期 100% 而其他接近 0,说明负载不均或存在串行瓶颈 - 容器环境下,
docker stats显示的 CPU% 是 cgroups 统计值,和 pprof 的采样视角不同,需交叉验证
多核利用率上不去,八成不是 goroutine 写少了,而是共享资源争用、channel 设计失当、或者根本没触发真正的 CPU 计算——先确认 workload 类型,再调参数,最后才优化调度细节。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










