sync 包

mutex 互斥锁

type Mutex struct {
	state int32     //互斥锁上锁状态枚举值如下所示
	sema  uint32    //信号量,向处于Gwaitting的G发送信号
}

const (
	mutexLocked = 1 << iota // 1 互斥锁是锁定的
	mutexWoken              // 2 唤醒锁
	mutexWaiterShift = iota // 2 统计阻塞在这个互斥锁上的goroutine数目需要移位的数值
)

互斥锁无冲突是最简单的情况了,有冲突时,首先进行自旋,因为大多数的Mutex保护的代码段都很短,经过短暂的自旋就可以获得;如果自旋等待无果,就只好通过信号量来让当前 goroutine 进入 Gwaitting 状态

once 实现

type Once struct {
	m    Mutex
	done uint32
}

// 实现了双重检测机制保证前期快速返回而且 f 没有执行完的时候不会被重复执行
func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 1 {
		return
	}
	// Slow-path.
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

Cond 条件变量

使用 sync.NewCond(l locker) 创建条件变量,参数传入互斥量或者读写锁都可以,返回 *sync.Cond 类型的对象

有三个方法:Wait,Signal,Broadcast 分别对应等待通知、单发通知以及广播通知的操作。 特殊场景需要惊群(全部唤醒)可以使用条件变量

Wait 可以让当前已锁定的 G 暂时先放弃读锁,并阻塞等待通知,此时写 G 可以进行写入,完成后再调用 signal 唤醒 G 继续读

与读写锁搭配时需要注意:

  • 一定要事先锁定与之关联的读锁后再调用 Wait 方法
  • 读完之后一定也要及时解除关联的锁

与 Wait 方法不同的是,调用 Signal 和 Broadcast 时无须锁定关联的锁

原子操作

常用于无锁编程中,修改时写时拷贝也是一种无锁化方案 让 uint 类型减少 N ?

// 利用二进制补码的特性绕过编译器检查
atomic.AddUint32(&ui32, ^uint32(-N-1))

CAS操作

有时我们需要在 for 循环里不停地进行 CAS 操作直到修改完成返回值为 true 才退出循环