[ Go语言中的sync | 青训营笔记 ]

82 阅读3分钟

sync 是 Go 语言标准库中的包,它提供了实现同步操作的基本工具集,包括 sync.Mutexsync.RWMutexsync.WaitGroupsync.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 包还提供了其他类型,例如 CondMap 等。在 Go 中使用 sync 进行并发控制,可以更方便地实现并发编程,避免资源竞争和死锁等问题。

浅谈原理

Go语言中的 sync包主要是通过互斥锁条件变量来实现同步和并发控制的。下面分别介绍一下这两个核心概念的实现原理。

  1. 互斥锁: 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并去唤醒他们,让他们去重新竞争锁。
  2. 条件变量: 条件变量sync.Cond 提供了一种通信机制,使得一个goroutine 能够等待另一个goroutine 的通知后才继续执行,Cond常常和互斥锁一起使用,他的内部也维护了一个等待队列,当一个goroutine 调用wait()方法时,它会释放互斥锁,并将自己加入到等待队列里面进行等待,当另一个goroutine 调用Signal() 或者 Broadcast() 方法时,它会从等待队列里面取出一个或多个 goroutine 并唤醒它们,让它们重新竞争互斥锁。

需要注意的是, Wait() 方法应当在持有互斥锁的情况下调用,否则会抛出运行时异常。在 Wait() 方法返回之后,当前 goroutine 重新持有了互斥锁,可以继续执行临界区代码了。