Go语言中的sync包是常用的一个内置包之一,其主要用于多协程(Goroutine),多并发之间的锁协调。

1.Locker

type Locker interface {
	Lock()
	Unlock()
}

Locker是一个接口,只包含两个方法

2.Mutex

type Mutex struct {
	state int32
	sema  uint32
}

Mutex 互斥锁
是常用类之一,对外只有两个方法 Lock,Unlock
Lock 即上锁,一但上锁其它协程在调用Lock就会进入阻塞状态,直到获取锁的协程调用Unlock,一般对于多协程调用的数据,读多写多或读少写多的情况下可用。
用法实例:

type SyncData struct {
    data int
    mu *sync.Mutex
}

func (s *SyncData) GetData() int {
    s.mu.Lock()
    defer s.mu.Unlock()
    return s.data
}

func (s *SyncData) SetData(d int) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.data = d
}

3.RWMutex

RWMutex 读写锁
相较于互斥锁,读写锁更为细化,对外暴露4个方法 Lock,Unlock,RLock,RUnlock,RLocker
Lock,Unlock 写锁
RLock,RUnlock 读锁
读锁与读锁之前不互斥,写锁与写锁,写锁与读锁均互斥。
RLocker 获取一个封装,将当前RWMutex转成Locker接口
RLocker源码:

type rlocker RWMutex

func (rw *RWMutex) RLocker() Locker {
	return (*rlocker)(rw)
}

那有意思的来了,即然有更细分的RWMutex了,那还需要使用Mutex了么?答案是需要,虽然RWMutex在使用上更为细化,但同时也增加了消耗。
100000次Lock测试中
Mutex用时 2048200 纳秒
RWMutex用时 2540800 纳秒
可见RWMutex落后了25%的性能,所以RWMutex常用于读多写少的场景中。而写多的场景中还是建议使用Mutex。

4.WaitGroup

WaitGroup 可以让阻塞协程,直到其它协程完成任务。
WaitGroup 对外暴露3个方法,Add(int),Done,Wait
其中Done方法其实就是Add(-1)
Done源码:

func (wg *WaitGroup) Done() {
	wg.Add(-1)
}

那现在主要来看 Add(int),Wait 两个方法
用法很简单,Add就是修改计数,参数int可以为正也可以为负,当计数回到0时,即任务全完成了。Wait停止阻塞。
Wait就是阻塞等待计数归零。
代码实例:

wg := sync.WaitGroup{}
for i := 0; i < 5; i++ {
    // 创建协程去执行一个任务
    go func() {
        wg.Add(1)
        DoSomething()
        wg.Done() // wg.Add(-1)
    }()
}
wg.Wait() // 阻塞到计数归零
fmt.Println("所有任务全完成了!")

5.Once

Once 正好它的名,Once可以确保在程序运行中,一个实例所控制的代码只执行一次。
对外只暴露1个函数 Do(func())
用法:

oc := sync.Once{}
oc.Do(func() {
    DoSomething()
})

注意,如果用新的Once实例覆盖oc,那在次使用Do时则又会执行

oc := sync.Once{}
oc.Do(func() {
    DoSomething()
})

oc = sync.Once{} // 覆盖oc
oc.Do(func() {
    DoSomething() // 此时又会执行一次 DoSomething
})

因为此时已经换了一个Once实例了,所以自然会在执行一次。

6.Cond

Cond 可以让多个协程阻塞,等待条件完成后执行
Cond 对外暴露3个方法 Wait,Broadcast,Signal 并对外暴露1个 Locker 成员 L
Wait方法即阻塞协程,Broadcast即唤醒所有被阻塞的协程,Signal则唤醒阻塞时间最长的那个协程。
用法和之前的几个略有不同,建议使用 sync.NewCond(Locker) 函数创建 Cond 实例。所需的参数 Locker 可以是 Mutex 也可以是 RWMutex,依据需求来决定使用哪个。
使用实例:

cond := sync.NewCond(&sync.Mutex{})
for i := 0; i < 5; i++ {
    go func() {
        cond.Wait()
        DoSomething()
    }()
}
cond.Signal()    // 唤醒一个协程
cond.Broadcast() // 唤醒所有协程

7.Pool

Pool 复用缓存池
Pool 对外暴露2个函数 Put(interface{}) Get()interface{}
以及一个成员 New func()interface{}
Get就是从缓存池中获取一个,Put则是向缓存池中放置一个。
如果Get时没有成员,那就会使用New来创建。
Pool主要用于复用一些数据,获取会从可用的数据中随机返回一个。
使用实例:

pool := sync.Pool{New: func() interface{} { return NewData()}}
v := pool.Get()  // 从复用池获取数据
DoSomething(v)   // 使用数据
pool.Put(v)      // 使用后归还数据

Q.E.D.


一只程序喵, 一个博客~