sync

sync #

  • sync.Mutex
  • sync.RWMutex
  • sync.WaitGroup
  • sync.Once
  • sync.Cond

锁的类型 #

Mutex 互斥锁 #

type Mutex struct {
	state int32
	sema  uint32 // semaphore 信号量
}
  • sync.Mutex.Lock 和 sync.Mutex.Unlock

state #

# 8 位
**** ****

*****               *              *           *
waitersCount  mutexStarving   mutexWoken   mutexLocked
  • mutexLocked — 表示互斥锁的锁定状态
  • mutexWoken — 表示从正常模式被从唤醒
  • mutexStarving — 当前的互斥锁进入饥饿状态
    • Goroutine 超过 1ms 没有获取到锁,它就会将当前互斥锁切换饥饿模式,防止被饿死,造成高尾延时
  • waitersCount — 当前互斥锁上等待的 Goroutine 个数 (最多 128 个)

RWMutex #

type RWMutex struct {
	w           Mutex
	writerSem   uint32
	readerSem   uint32
	readerCount int32
	readerWait  int32
}
  • 写操作使用 sync.RWMutex.Lock 和 sync.RWMutex.Unlock 方法;
  • 读操作使用 sync.RWMutex.RLock 和 sync.RWMutex.RUnlock 方法;

  • 调用 sync.RWMutex.Lock 尝试获取写锁时;
    • 每次 sync.RWMutex.RUnlock 都会将 readerWait 其减一,当它归零时该 Goroutine 就会获得写锁;
    • 将 readerCount 减少 rwmutexMaxReaders 个数以阻塞后续的读操作;
  • 调用 sync.RWMutex.Unlock 释放写锁时,会先通知所有的读操作,然后才会释放持有的互斥锁;

读写互斥锁在互斥锁之上提供了额外的更细粒度的控制,能够在读操作远远多于写操作时提升性能。


WaitGroup #

type WaitGroup struct {
	noCopy noCopy // wg 无法复制
	state1 [3]uint32
}

requests := []*Request{...}
wg := &sync.WaitGroup{}
wg.Add(len(requests))

for _, request := range requests {
    go func(r *Request) {
        defer wg.Done()
        // res, err := service.call(r)
    }(request)
}
wg.Wait()

Done #

sync.WaitGroup.Done 只是对 sync.WaitGroup.Add 方法的简单封装, 我们可以向 sync.WaitGroup.Add 方法传入任意负数(需要保证计数器非负)快速将计数器归零以唤醒其他等待的 Goroutine;


Once #

  • sync.Once.Do 方法中传入的函数只会被执行一次,哪怕函数中发生了 panic
  • 两次调用 sync.Once.Do 方法传入不同的函数也只会执行第一次调用的函数;
type Once struct {
	done uint32
	m    Mutex
}

Cond #

type Cond struct {
	L       Locker // 用于保护内部的 notify 字段
	notify  notifyList
	noCopy  noCopy // 保证结构体不会在编译期间拷贝
	checker copyChecker // 禁止运行期间发生的拷贝
}

type notifyList struct {
	wait uint32
	notify uint32

	lock mutex
	head *sudog
	tail *sudog
}

  • sync.Cond.Wait 将当前 Goroutine 陷入休眠状态
    • 在调用之前一定要使用获取互斥锁,否则会触发程序崩溃
  • sync.Cond.Signal 唤醒队列最前面的 Goroutine
  • sync.Cond.Broadcast 唤醒队列中全部的 Goroutine


Mutex #

Mutex 互斥锁 #

  • 在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个 goroutine 释放该 Mutex
  • 使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
    • 同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
  • 在 Lock() 之前使用 Unlock() 会导致 panic 异常
  • 已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁
  • 适用于读写不确定,并且只有一个读或者写的场景
type Mutex struct {
    // contains filtered or unexported fields
}

func (m *Mutex) Lock()

func (m *Mutex) Unlock()

RWMutex 读写锁(读多写少) #

RWMutex 基于 Mutex 实现

  • RWMutex 是单写多读锁,该锁可以加一个写锁或者多个读锁
  • 写锁会阻止其他 goroutine(无论读和写)进来,整个锁由该 goroutine 独占
    • Lock() 加写锁,Unlock() 解写锁
    • 在 Lock() 之前使用 Unlock() 会导致 panic 异常
    • 如果在加写锁之前已经有其他的读锁和写锁,则 Lock() 会阻塞直到该锁可用,为确保该锁可用,已经阻塞的 Lock() 调用会从获得的锁中排除新的读取器,即写锁权限高于读锁,有写锁时优先进行写锁定
  • 读锁占用的情况下会阻止写,不会阻止读,多个 goroutine 可以同时获取读锁
    • RLock() 加读锁,RUnlock() 解读锁
    • RLock() 加读锁时,如果存在写锁,则无法加读锁
    • 在没有读锁的情况下调用 RUnlock() 会导致 panic 错误
      • RUnlock() 的个数不得多余 RLock(),否则会导致 panic 错误
  • 适用于读多写少的场景


Cond #

type Cond struct {

	// L is held while observing or changing the condition
	L Locker
	// contains filtered or unexported fields
}

// Broadcast 会唤醒所有等待 c 的 goroutine
func (c *Cond) Broadcast()

// Signal 只唤醒 1 个等待 c 的 goroutine
func (c *Cond) Signal()

// Wait() 会自动释放 c.L,并挂起调用者的 goroutine。之后恢复执行,Wait() 会在返回时对 c.L 加锁。
// 除非被 Signal 或者 Broadcast 唤醒,否则 Wait() 不会返回
func (c *Cond) Wait()
  • 条件
    • 一个条件一定要有一个信号
  • 信号
    • wait 等待的是信号
    • signal 发送的是信号
    • 一个信号可以对应到多个条件

sync.Cond vs sync.Mutex #

  • Mutex
    • one goroutine for each write and read
  • Cond
    • multiple readers wait for the shared resources to be available

参考 #


本文访问量

本站总访问量

本站总访客数