在文件
sync_runtime_canSpin
的方法/usr/local/go/src/runtime/proc.go
中,为什么注释对应
gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1
是GOMAXPROCS>1
?sched.npidle+sched.nmspinning
不应该等于gomaxprocs
吗?这个条件的作用是什么?
回想一下,Go 的 sync.Mutex
类型实现中的
主动旋转的目的是在尝试获取互斥锁时发现锁争用时,最大限度地减少与交换 goroutine 相关的低效率。
这与“合作互斥体”形成鲜明对比,在“合作互斥体”中,尝试获取互斥体时遇到的争用将导致 goroutine 立即屈服于调度程序,调度程序可能会找到一些其他工作要做。这是 Go 1.5 版本之前的行为。问题是调度其他工作的成本很高,并且可能会导致缓存未命中和缓存一致性流量,从而减慢程序的整体进度。如果锁很快释放并且可以立即获取,那么自旋几个周期可能会提高调度效率。
为什么gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1
对应的评论是
考虑到这一背景,当互斥体由于另一个 goroutine 持有它而无法立即锁定时,GoGOMAXPROCS>1
sync.Mutex
实现会调用
runtime_sched_canSpin
(ref)。结果决定 goroutine 是否应该忙等待,让 CPU 保持一些周期,看看锁是否很快被释放。如果互斥量“饥饿”,则不会发生这种情况,这意味着在很长一段时间内没有获取互斥量(此处省略了进一步的讨论,因为不相关)。 在这种情况下,条件检查 (
gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1
) 的目的有两个:
它确保
GOMAXPROCS
大于1您问为什么不等式
gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1
GOMAXPROCS>1
的要求。如上所述,这种不等式的目的实际上有两个,它还实现了检查至少有一个其他 P 正在运行并主动执行 Go 代码(因为这给出了一些启发,即某些代码正在运行,可能会释放互斥锁)很快,所以等待该事件是值得的)。
考虑 P 可能处于两种状态之一:idle
,即P当前没有执行任何goroutine)。在这种情况下,它持有一个 P,因此 P 当前不处于活动状态。然而,M 正在积极寻找工作,因此额外的工作(可能导致锁被释放)可能很快就会变得可以运行。
根据定义,P
的数量等于
GOMAXPROCS
:
|P| == GOMAXPROCS
。我们知道 Ps 可以空闲、运行或分配给正在旋转的 M:|P| GOMAXPROCS == P-idle + P-running + M-spinning
如果我们希望至少有一个
otherP 能够工作,我们需要
P-running > 1
。这意味着一个 P 正在运行尝试获取锁(runtime_sync_canSpin
中的那个),而另一个 P 正在积极运行其他一些 Go 代码,这可能会导致锁很快被释放。
为了反转条件以识别我们不应该旋转的情况(即没有其他 P 正在运行),
>
变为
<=
并且我们有
GOMAXPROCS <= P-idle+M-spinning+1
。关于 GOMAXPROCS > 1
的要求,出于论证的目的,假设 GOMAXPROCS = 1
并且条件为真。那么这意味着
P-running > 1
,这是不可能的,因为只能有一个 P。逻辑矛盾。因此,如果条件为真,GOMAXPROCS > 1
。
if
声明中的其他条件if
条件语句中的其他两项是:i >= active_spin
实现了有限旋转行为——如果预定次数的旋转尝试没有导致获得锁,则不会执行进一步的旋转以允许工作线程(和CPU)进行其他有用的工作程序。
ncpu <= 1
确保使用多核系统。在单核系统上,自旋将占用所有 CPU 时间,因此持有锁的 goroutine 在自旋期间不会在释放锁方面取得进展。