sync是 Go 语言标准库中的包,它提供了实现同步操作的基本工具集,包括sync.Mutex、sync.RWMutex、sync.WaitGroup、sync.Once等常用类型。
常用类型说明:
下面简单介绍一些常用的 sync 类型:
sync.Mutex:互斥锁,用于保护一段临界区代码,同一时间只允许一个 goroutine 进入临界区。通过Lock()加锁、Unlock()解锁来实现互斥访问。sync.RWMutex:读写锁,用于细粒度控制并发读写共享资源。多个 goroutine 能同时读取,但只允许一个 goroutine 写入。通过RLock()读锁、RUnlock()解读锁、Lock()写锁、Unlock()解写锁来实现读写控制。sync.WaitGroup:等待组,用于等待一批 goroutine 完成运行后再执行接下来的逻辑。通过Add()增加计数、Done()减少计数、Wait()等待计数为 0,以实现等待多个 goroutine 执行完成。sync.Once:一次性操作,用于实现某个操作只执行一次。通过Do()方法来执行操作,只有第一次调用Do()时会执行操作,之后调用Do()会直接返回。
除了上述常用类型, sync 包还提供了其他类型,例如 Cond 、 Map 等。在 Go 中使用 sync 进行并发控制,可以更方便地实现并发编程,避免资源竞争和死锁等问题。
浅谈原理
Go语言中的 sync包主要是通过互斥锁和条件变量来实现同步和并发控制的。下面分别介绍一下这两个核心概念的实现原理。
- 互斥锁:
sync.Mutex内部维护了一个状态变量,用于表示锁是否呗占用,锁被占用时,其他goroutine就获取不到锁,需要等待占用锁的goroutine解锁才能再次竞争锁。具体石先生,Mutex会使用一个整型类型的状态变量state,其中最高位bit表示锁是否被占用,剩下的bit表示等待锁的goroutine数量,在初始化时,state被初始化为0,表示未被占用,等待groutine数量为0。 当一个goroutine调用Lock()方法时,它会尝试将state的最高位设置为1,如果设置成功就说明锁被占用了,该goroutine可以执行临界区代码了。如果state最高位bit已经是1了,说明锁已经被占用了,那么该goroutine就需要将自己加入到等待队列里面,并阻塞等待锁的释放,当锁被占用的goroutine调用Unlock()方法时,它会将state的最高位bit重新设置为0,然后依次从等待队列中却出goroutine并去唤醒他们,让他们去重新竞争锁。 - 条件变量: 条件变量
sync.Cond提供了一种通信机制,使得一个goroutine 能够等待另一个goroutine的通知后才继续执行,Cond常常和互斥锁一起使用,他的内部也维护了一个等待队列,当一个goroutine调用wait()方法时,它会释放互斥锁,并将自己加入到等待队列里面进行等待,当另一个goroutine调用Signal()或者Broadcast()方法时,它会从等待队列里面取出一个或多个goroutine并唤醒它们,让它们重新竞争互斥锁。
需要注意的是, Wait() 方法应当在持有互斥锁的情况下调用,否则会抛出运行时异常。在 Wait() 方法返回之后,当前 goroutine 重新持有了互斥锁,可以继续执行临界区代码了。