Go源码解读之sync中的基本类型和使用场景
Table of Contents
Overview⌗
sync包提供基本的同步原语,例如互斥锁。
除了Once和WaitGroup类型外,大多数都是供低级库例程使用的。
更高层次的同步最好通过channels和通信来完成。
从代码看,sync提供了几种类型:
- Cond:条件变量
- Locker:锁的接口定义
- Map:协程并发安全的Map
- Mutex:互斥锁
- Once:单次执行
- Pool:池
- RWMutex:读写锁
- WaitGroup:等待组
几个类型分别对应不同的使用场景。
sync.Cond⌗
Cond实现了一个条件变量,是等待或宣布一个事件发生的goroutines的汇合点。
通俗的说,sync.Cond用来协调那些访问共享资源的goroutine,当共享资源发生变化时,通知被阻塞goroutine。
sync.Cond 经常用在多个 goroutine 等待一个 goroutine 通知(事件发生)的场景。
sync.Map⌗
Map就像Go中的map[interface{}]interface{}
,但对于多个goroutine的并发使用是安全的,不需要额外的锁或协调。
使用map + sync.Mutex或者sync.RWMutex的方式也可以实现与sync.Map类似的功能,但是在某些场景下,sync.Map具有更高的性能:
Map类型针对两种常见用例进行了优化:
- 当给定key的条目仅被写入一次却被读取多次时,例如在仅增长的高速缓存中
- 当多个goroutine读取,写入和覆盖的key都不相关时
在这两种情况下,与与单独的Mutex或RWMutex + map 相比,使用Map可以显着减少锁争用。
sync.Mutex⌗
Mutex是一个相互排斥的锁。Mutex的零值是一个解锁的Mutex。
当调用Lock方法进行加锁时,如果锁已在使用中,则goroutine会阻塞,直到锁可用为止。 当调用UnLock方法进行解锁时,如果锁没有在使用,则会出现运行时错误。
锁定的互斥锁与特定的goroutine没有关联。允许一个goroutine锁定Mutex,然后安排另一个goroutine对其进行解锁。
sync.RWMutex⌗
RWMutex是一个读写器相互排斥的锁。 该锁可以由任意数量的读者或单一的写者持有。RWMutex的零值是一个解锁的mutex。
读读不互斥,读写互斥,写写互斥。
sync.Once⌗
Once的Do(f)方法保证只运行一次,即使f发生panic。 这常用在单例模式,配置文件加载,初始化这些场景下。
sync.Pool⌗
Pool是一组可以单独保存和检索的临时对象。 储存在池子里的任何对象都可能在任何时候被自动删除,而无需通知。 池可以安全地同时被多个goroutine使用。
Pool的作用是缓存已分配但未使用的项目,以便以后再使用,减轻垃圾收集器的压力。也就是说,它使建立高效、线程安全的自由列表变得容易。
池的一个适当的用途是管理一组临时项目,这些临时项目在包的独立客户端之间默默地共享,并可能被重复使用。Pool提供了一种在许多客户端之间分摊分配开销的方法。
当然,Pool并不适用于一些短命的对象池化。
相当于拿出来,做操作,再放回去,操作过的东西放回去的时候是啥样,拿出来的时候就是啥样的。也就是说,拿出来用的时候需要初始化数据或者清空。 如Gin的源码:https://github.com/gin-gonic/gin/blob/v1.7.1/gin.go#L439
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// get Context from pool
c := engine.pool.Get().(*Context)
// reset
c.writermem.reset(w)
c.Request = req
c.reset()
// use c
engine.handleHTTPRequest(c)
// put back
engine.pool.Put(c)
}
sync.WaitGroup⌗
一个WaitGroup等待一个goroutine的集合完成。 主的goroutine调用Add来设置要等待的goroutine的数量。 然后每个goroutine运行,完成后调用Done。
同时,可以用Wait来阻塞,直到所有的goroutine都完成。
更新于2021/05/12 23:45