正常模式(非公平锁)
正常模式下,所有等待锁的goroutine按照FIFO(先进先出)顺序等待。唤醒的goroutine不会直接拥有锁,而是会和新请求goroutine竞争锁。新请求的goroutine更容易抢占:因为它正在CPU上执行,所以刚刚唤醒goroutine有很大可能在锁竞争中失败。在这种情况下,这个被唤醒的goroutine会加入到等待队列的前面。
饥饿模式(公平锁)
为了解决了等待goroutine队列的长尾问题饥饿模式下,直接由unlock把锁交给等待队列中排在第一位的goroutine (队头),同时,饥饿模式下,新进来的goroutine不会参与抢锁也不会进入自旋状态,会直接进入等待队列的尾部。这样很好的解决了老的goroutine一直抢不到锁的场景。饥饿模式的触发条件:当一个goroutine等待锁时间超过1毫秒时,或者当前队列只剩下一个goroutine的时候,Mutex切换到饥饿模式。
这种说法 不完全正确,并且有一些误解。Go 的 sync.Mutex
本身并没有明确的机制来判断锁是否进入饥饿模式,也没有内建的机制来检测一个 goroutine 等待锁的时间是否超过某个阈值(如 1 毫秒)。Go 的 Mutex
并不会自动在某些条件下切换到饥饿模式,且也没有在标准库中实现 "饥饿模式" 的自动切换。
Go 中 sync.Mutex
的工作原理
Go 的 sync.Mutex
是一个 非公平 锁,也就是说,Go 的 Mutex
并不会保证按照请求锁的顺序来分配锁。Mutex
的主要目标是保证并发安全,不会有显式的控制来避免饥饿(例如,锁的等待时间超过某个阈值)。
在 sync.Mutex
的实现中,当一个 goroutine 获取了锁时,其他请求锁的 goroutine 会被阻塞,直到锁被释放为止。当一个 goroutine 释放锁时,操作系统的调度器(而非 Mutex
本身)决定哪个等待的 goroutine 会获取锁。Go 并没有提供内建的公平性机制。
关于饥饿模式
-
饥饿模式的触发时机: Go 标准库的
sync.Mutex
并没有实现一个 "饥饿模式" 的自动切换。所谓饥饿模式(Starvation Mode)一般是指某些 goroutine 因为竞争条件或调度策略的原因,永远无法获得锁,导致它们在某些情况下得不到执行。Go 的Mutex
并不会自动进入饥饿模式,也不会因为某个 goroutine 等待过久而强制切换。但是,某些情况可能会导致饥饿:
-
长时间持有锁的 goroutine:如果一个 goroutine 持有锁的时间很长,其他请求锁的 goroutine 可能会长时间等待,导致这些 goroutine 在某些情况下看起来像是进入了饥饿模式。
-
无限循环的竞争:如果某些 goroutine 一直获取到锁,其他 goroutine 就会一直等待,造成饥饿问题。
但这种情况完全依赖于程序设计,Go 本身没有内建的机制来控制或检测 "等待锁的时间"。
-
-
“只有一个 goroutine 队列”: 当队列中只剩下一个 goroutine 时,锁本身的行为与正常模式类似,因为没有其他竞争者,它会立即获取锁。Go 不会进入饥饿模式,反而是这种情况最为简单,
Mutex
就像正常的同步机制一样,顺利执行。 -
"等待超过 1 毫秒": Go 的
sync.Mutex
并没有内建的机制去追踪一个 goroutine 等待锁的时间是否超过 1 毫秒等阈值,也没有基于时间来决定是否切换锁的行为。如果某个 goroutine 阻塞时间很长,通常是因为某些其他原因,如:-
锁的竞争非常激烈。
-
锁被长时间持有。
-
操作系统调度的问题。
Go 的
Mutex
本身没有为这种场景提供专门的机制。 -
如何避免饥饿模式
如果你担心饥饿模式,可以尝试以下方法来确保公平性:
-
公平锁(Fair Mutex): 可以自己实现公平的锁,保证 goroutine 按照请求锁的顺序来获取锁。例如,可以使用
channel
或sync.Cond
等机制来实现一个公平的锁调度。 -
重试机制: 在某些情况下,可以设计重试机制,允许等待过长时间的 goroutine 在没有获取到锁时重新尝试。通过这种方式可以避免长时间等待的情况,减少饥饿现象。
-
减少锁粒度: 尽量减少锁持有的时间,避免长时间持有锁而阻塞其他 goroutine。如果可能,可以将锁的粒度降低,将大的临界区分割成多个小的临界区,从而减少竞争。
-
优先级队列: 可以使用优先级队列来调度需要锁的 goroutine,以确保某些重要的任务优先获得执行机会。
总结
-
Go 中的
sync.Mutex
没有内建的机制会根据等待时间切换到饥饿模式,也没有内建的时间阈值来判断某个 goroutine 是否等待了过久。 -
Go 的
Mutex
是一个 非公平的锁,即没有保证按照请求锁的顺序进行锁分配,这可能会导致一些 goroutine 长时间无法获得锁,从而出现类似饥饿的问题。 -
饥饿模式 本身是开发者需要注意的问题,通过合理的锁设计、优先级管理等方式可以减少饥饿现象。
Go 没有内建的机制来监控锁的等待时间或进入饥饿模式,因此开发者需要自己注意并发的公平性。
总结
对于两种模式,正常模式下的性能是最好的,goroutine可以连续多次获取锁,饥饿模式解决了取锁公平的问题,但是性能会下降,这其实是性能和公平的一个平衡模式。
标签:goroutine,模式,Golang,饥饿,Mutex,Go,mutex,等待 From: https://blog.csdn.net/qq_38609643/article/details/144962731