gomaxprocs仅设置p的数量,不绑定物理cpu核心;真要绑核需用taskset、cpuset或syscall.schedsetaffinity,且其值不应超过实际可用核心数,否则加剧调度竞争。

GOMAXPROCS 设置后,调度循环不会自动绑定到特定 CPU 核心
Go 运行时的调度循环(即 schedule 函数)本身不负责 CPU 亲和性(affinity)——它只决定哪个 G 在哪个 P 上运行,而 P 绑定的 M(OS 线程)最终由操作系统调度器落到哪颗物理/逻辑核心上。你调用 runtime.GOMAXPROCS(8),只是告诉 runtime 最多允许 8 个 P 同时执行 Go 代码;这 8 个 P 对应的 M 可能被 OS 调度到任意核心,甚至频繁迁移。
- 设置
GOMAXPROCS不等于“把 P 钉死在某个 core”,taskset或cpuset才管这事 - 若需控制核心绑定,得在启动进程时用
taskset -c 0-3 ./myapp,或在容器中通过cpuset.cpus限制 - Go 调度器内部的
findrunnable、execute等函数只做 G/P/M 的状态流转,不读取 /proc/cpuinfo 或调用 sched_setaffinity - 观察实际落核情况,可用
perf top -p $(pgrep myapp)或htop的 “F4 → Sort by: LAST USED CPU”
多核下 schedule 循环实际只在有可运行 G 时才活跃
每个 P 拥有自己的本地运行队列(LRQ),它的 schedule 循环不是常驻轮询,而是按需触发:当 P 的 LRQ 为空时,它会先尝试从全局队列(GRQ)偷一个 G,再尝试从其他 P 的 LRQ “窃取”(work-stealing)。若全空,则该 P 进入休眠,对应 M 可能被 OS 调度走——此时该核心上几乎无 Go 调度开销。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- 这意味着:16 核机器设
GOMAXPROCS=16,但若只有 2 个活跃 G,通常只有 1–2 个 P 在跑,其余 P 处于 idle 状态 -
go tool trace中 “Proc status” 面板里长期显示灰色(idle)的 P,说明它们没活干,不是 bug 是设计 - 高频空转(如
for {})会让单个 P 占满一个核心,但不会自动触发其他 P 去分担——因为没 G 可 steal,也没新 G 创建 - 真正并行压满多核,依赖的是大量可并行的、非阻塞的 G,而不是靠调度器“推着跑”
抢占点缺失时,schedule 循环对死循环 G 完全失效
Go 1.14+ 引入异步抢占,靠向 M 发送 SIGURG 并等待下一个安全点(safepoint)触发 gosched_m。但如果 G 死在纯算术循环里(如 for { i++ } 或 for { atomic.AddUint64(&x, 1) }),它根本不会进入任何 safepoint,schedule 就无法插入调度逻辑——那个 G 会一直霸占当前 P,直到被系统信号中断(如 syscalls)或发生栈增长。
- 这不是调度器“不工作”,而是它本就不该、也不能在任意指令处打断用户代码
-
runtime.Gosched()是协作式让出,不解决抢占问题;time.Sleep(0)可行,因为它会进入_Gwaiting状态,强制交出 P - 用
go tool trace查看 “Goroutines” 面板,若某 G 的生命周期极长且状态始终是running,大概率就是卡在无 safepoint 的循环里 - 编译时加
-gcflags="-S"看汇编,确认循环体是否含CALL指令(函数调用)——没有就基本没 safepoint
最易被忽略的一点:调度循环的行为高度依赖 G 的状态分布与阻塞类型。它不是“越忙越好”,而是“越符合其设计假设(大量短生命周期、含 I/O 或 channel 等安全点)越高效”。盲目堆高 GOMAXPROCS 或期待它兜底 CPU 密集型失控逻辑,只会掩盖真实瓶颈。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










