1 Mutex(互斥锁)在Go语言中主要存在两种模式:
-
正常模式(Normal Mode / Non-Preemptive Fairness / Non-Blocking Fairness):
- 在正常模式下,当一个goroutine尝试获取已经被锁定的Mutex时,它首先会进行自旋(spinning),即短时间内多次尝试通过原子操作快速获得锁。
- 若自旋失败,则goroutine会被放入等待队列中。在这个模式下,虽然队列遵循FIFO(先进先出)原则,但是从队列中唤醒的goroutine并不会立即获得锁,而是与新到达并尝试获取锁的goroutines一起竞争锁资源。这意味着即使 goroutine 在队列中的等待时间较长,也可能会被后来的新请求抢占,导致可能的“饥饿”现象。
-
饥饿模式(Starving Mode / Blocking Fairness / Preemptive Fairness):
- 饥饿模式是Go语言为了缓解上述正常模式可能导致的饥饿问题而引入的一种优化措施,确保等待锁时间较长的goroutine能够有机会获得锁。
- 当Mutex检测到某些goroutine已经等待了一定时间且未能成功获取锁时,它们将更有可能优先得到锁。具体实现细节和版本之间可能有所不同,但基本思路是避免长时间等待的goroutine持续饥饿,提高锁的公平性。
需要注意的是,不同资料对于Mutex的具体工作模式描述有所差异,尤其是关于饥饿模式的实现和触发条件,在不同的Go语言版本中可能有不同的机制。例如,有的资料提到在特定条件下(如等待时间超过某个阈值)进入饥饿模式,有的则表示在特定版本后调整了Mutex的行为以更好地防止饥饿情况。实际行为应参考最新的官方文档或源代码来确认。
2 go 里锁的类型
在Go语言中,标准库sync
包提供了两种主要类型的锁来控制并发访问共享资源:
-
互斥锁(Mutex):
sync.Mutex
是最基本的互斥锁类型,它保证在同一时刻最多只有一个协程能够获取到锁并访问受保护的资源。当一个协程获取了Mutex后,其他尝试获取该锁的协程将被阻塞,直到锁被释放。
使用示例:
Go1var mu sync.Mutex 2 3func doSomething() { 4 mu.Lock() 5 defer mu.Unlock() 6 7 // 在这里执行对共享资源的独占操作 8}
-
读写锁(RWMutex):
sync.RWMutex
是一种更复杂的锁,支持多个读取者同时访问(读锁),但同一时间只允许一个写入者访问(写锁)。这意味着如果有任何写操作正在进行或等待进行时,所有读操作都必须等待。
使用示例:
Go1var rwmu sync.RWMutex 2 3func readData() { 4 rwmu.RLock() 5 defer rwmu.RUnlock() 6 7 // 在这里执行对共享资源的读取操作 8} 9 10func writeData() { 11 rwmu.Lock() 12 defer rwmu.Unlock() 13 14 // 在这里执行对共享资源的写入操作 15}
此外,在特定场景下,还有其他的同步原语可以作为某种意义上的“锁”使用,比如:
- 条件变量(Cond):
sync.Cond
可以让一组 goroutines 阻塞等待某个条件满足,并由另一个goroutine来通知它们条件已经满足,从而实现更加灵活的同步控制。 - Once:
sync.Once
用于确保某段代码只被执行一次,即使在并发环境中也能够得到保障。
以上都是Go语言中直接与锁相关的同步机制。