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) // 使用后归还数据