青训营go语言基础总结六:sync锁底层实现| 豆包MarsCode AI 刷题

78 阅读3分钟

sync.Mutex:

Mutex(互斥锁)是一种最基本的同步机制,用于确保同一时间只有一个 Goroutine 能够访问特定的资源。

互斥锁的底层实现原理是基于操作系统中的原子操作和信号量机制来实现的。当一个协程调用mutex.Lock()时,它会尝试获取互斥锁的状态,如果互斥锁当前处于未锁定状态,则协程会将其状态设置为已锁定,并继续执行;否则该协程会被放入等待队列中,等待其他协程释放锁。

当一个协程调用mutex.Unlock()时,它会释放互斥锁的状态,并且唤醒等待队列中的一个协程。被唤醒的协程可以再次尝试获取互斥锁的状态,并继续执行。

Go中的sync.Mutex的结构体为:

type Mutex struct {
	state int32
	sema  uint32
}

Sync.Mutex由两个字段构成,state用来表示当前互斥锁处于的状态,sema用于控制锁状态的信号量

锁的两种模式

互斥锁在设计上主要有两种模式: 正常模式和饥饿模式。

之所以引入了饥饿模式,是为了保证goroutine获取互斥锁的公平性。所谓公平性,其实就是多个goroutine在获取锁时,goroutine获取锁的顺序,和请求锁的顺序一致,则为公平。

正常模式下,所有阻塞在等待队列中的goroutine会按顺序进行锁获取,当唤醒一个等待队列中的goroutine时,此goroutine并不会直接获取到锁,而是会和新请求锁的goroutine竞争。 通常新请求锁的goroutine更容易获取锁,这是因为新请求锁的goroutine正在占用cpu片执行,大概率可以直接执行到获取到锁的逻辑。

饥饿模式下, 新请求锁的goroutine不会进行锁获取,而是加入到队列尾部阻塞等待获取锁。

饥饿模式的触发条件:

当一个goroutine等待锁的时间超过1ms时,互斥锁会切换到饥饿模式

饥饿模式的取消条件:

当获取到锁的这个goroutine是等待锁队列中的最后一个goroutine,互斥锁会切换到正常模式

当获取到锁的这个goroutine的等待时间在1ms之内,互斥锁会切换到正常模式

sync.RWMutex:

RWMutex 是读写互斥锁,允许任意数量的 Goroutine 同时读取资源,但仅允许一个 Goroutine 修改资源。

读写锁的底层实现原理是在互斥锁的基础上增加了读写等待队列。 当一个协程调用rwMutex.RLock()时,它会尝试获取读锁。如果没有其他协程持有写锁,则当前协程可以成功获取读锁,并继续执行;否则该协程会被放入读等待队列中。

当一个协程调用rwMutex.RUnlock()时,它会释放读锁,并唤醒等待队列中的其他协程。被唤醒的协程可以再次尝试获取读锁。

类似地,当一个协程调用rwMutex.Lock()时,它会尝试获取写锁。如果没有其他协程持有读锁或写锁,则当前协程可以成功获取写锁,并继续执行;否则该协程会被放入写等待队列中。

当一个协程调用rwMutex.Unlock()时,它会释放写锁,并唤醒等待队列中的其他协程。被唤醒的协程可以再次尝试获取读锁或写锁。

image.png sync包下的其他 sync.WaitGroup:

WaitGroup 用于等待一组 Goroutine 完成它们的工作。

sync.Map:

sync.Map 提供了一种并发安全的映射数据结构,适用于读多写少的场景。

sync.Once:

Once 类型保证某个动作在其生命周期中只执行一次,典型的应用场景是单例模式或者初始化过程。

sync.Cond:

Cond 是条件变量,基于互斥锁实现,允许 Goroutine 在满足某个条件时进行等待和通知。