自旋不是锁,而是一次有边界的下注
抢锁时是否该在“门口”死等?这个选择可类比为一个仅有一个隔间的公共卫生间:门一直紧锁在线争用上。有人选择站在门前不断转动门把手,一旦门开便立刻冲入;与之相对的是先到沙发躺下,请系统在门开时唤醒自己。前者就是自旋(spinning)。原文指出,自旋有别于锁的名字本身,它是一场限定条件的赌局:赌对方很快放弃占用,从而省去唤醒切换的时间,若赌败则浪费处理能力。

这两种“等法”的利弊很清楚。自旋的好处是在门开的一瞬间就能抢占,甚至以零点零零一秒级别的延迟完成;付出的是当前等待方占满一个 CPU 核、并无实际产出的代价。相比之下,选择先处置其他业务或折叠等待,便无需烧 CPU;不足是被系统唤醒需要时间,须从等待队列里捞人、安排重新执行。作者以此说明,自旋并无免费午餐。
自旋值与输入时长强相关:数百纳秒对比数毫秒
收益对比取决于持锁者的占用时长——原文分别举例为极短的“一趟几分钟以内的行为”与较长的占用。例如拿锁者做的事仅涉及几次操作、场景里为系鞋带(可用纳秒量级),则自旋胜出;而保持锁的时间拉长至几毫秒以上(原文给出的场景为洗澡),自旋便不再经济和公平,因为死等倒不如让线程挂起稍后激活。也正因为如此,绝对别让自旋无边界蔓延。

但忽略一种特殊死结会让自旋失效:当一个 P 上运行的 goroutine 死守自旋发起锁请求,而能解锁的 goroutine 又恰好仍排在该 P 的运行队列中,持锁的 work 无法取得执行权。于是一边绕着查锁,一边锁永远不会释放,形成的是一个调度层面发生的“滑”。原文把这个带根本的原因写作一条基本前提:只有确定握锁者在另与其中一个 P 上真正代表执行时,自旋的推理才能成立。
为什么 Go 不提供让你放手动“旋转螺抓·CPU”的 sync.SpinMutex
许多初学者会采用循环自己拧:不用标准库锁而是写循环调用 CompareAndSwap,看上去有点硬实际上问题密集。原文以代码思维为切口,点出反模式——首先,Go 的标准库并未暴露自旋调控原语;使用者在用户层不得不努力控制。遇到高竞争时,这种做法像一阵子刺入到内核缓存线的持锁风暴,反而成为解锁者的阻碍。更直接的空白处在于,用户态无法掌控 runtime/调度器的状态,只剩下猜测的味道。作者措辞偏快,意思是如果要用就足够毁掉上层策略,因为决策多数与正常流量并行状态紧密隔离。

回到发问之处:Go 内置的是两层混合行为,供 runtime 默默调控。多人共用的 interface 调用 mu.Lock() 最初会尝试即时抢占权;能一步 CAS(一次 compare-and-swap)立即成功就马上过。真正出现其他计时者发起重量式争用触发条件先后顺序可以归类:先做几次 Paen 的循环序列作为特定等待窗口,且数量可控。但原文的档口相当精准——“最多 4 轮、每轮几次 PAUSE+约环请求”,如此短的范围尽量精简。实际属于重量级延时则必须换向别的计算:如果约依旧没有占到资源,那就自身出价,转为 park(休眠),挂到信号内存里由等待列表排序。

更进一步,为避免等待线程被无穷的空翻耗尽耐性,Go 也为 sync.Mutex 设定饥饿阈值。根据其可竞争运行模式文档:等待一个 goroutine 之间超过1ms(原文即“超过1ms”),该锁进入所谓饥饿模式;这时 newcomer 新路过者不得再任性地查或进行“插队抢权”,而是直接将锁如递物般交给队列直接排头的排队等待者手上。
当自旋程序自行判断还有没有条件和合不算时通常先看外层类集合目标:若自己所在 P 上有尚无执行的任务并且有可能会执行的候选者或是其他活儿仍开放占用,那么就应该利用在这段空档里轮寻一次而已;若是其他条件或外部处理器不可执行的不可预见状,线程内部也会返回早期选择直接休息。如原始材料指出运行时做了实预判断,并非每次绝对都要等待到一个窗口之后才肯调下工作全部步骤。

原则:短场景合法例外,其余情形始终应改回 sync.Mutex
只要对实现放置过劳成本的无用消耗来对照查看,我们就倾向别在抽象的业务一线使用自旋手搓流程。真如临时区的引用场景若是临界区极短,能被称作仅修一时单走一例数值,情况至支持。另一者方面,前提是你确信持锁者正在其他 P 的行为对照面,你也须允许亲自执行代码来推断这类证据。按原资料相关用法所给第三点更具门槛:意味着相当愿意接触运行时内部语义及不可运行状态的程序方有可能写与之对应的特殊配置工具句条指令布局。但在默认可商业实践中首选还是让 Go 自行组合现有的先设机制——即多用于调用 `sync.Mutex`。
小结:讨论线程争夺期间既表现为谁保持锁而分配决策——将 CPU 开销边界确定后绑定进到唯一指定运行时,而不是外部条件中间去试图强制进行。运行时里面负责限:两通道切换拥有各自的权衡份额。借用这一点可知它宁愿在少发方案里限制确定排队重转、拒新“螺旋手”,依赖编码人直接采取一个接口调用进行管控。
