1、sync.WaitGroup
Go语言中可以使用sync.WaitGroup
来实现并发任务的同步。
sync.WaitGroup
有以下几个方法:
方法名 | 功能 |
---|---|
func (wg * WaitGroup) Add(delta int) | 计数器+delta |
(wg *WaitGroup) Done() | 计数器-1 |
(wg *WaitGroup) Wait() | 阻塞直到计数器变为0 |
sync.WaitGroup
内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了 N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用 Done 方法将计数器减1。通过调用 Wait 来等待并发任务执行完,当计数器值为 0 时,表示所有并发任务已经完成。
需要注意sync.WaitGroup
是一个结构体,进行参数传递的时候要传递指针。
2、sync.Once
在某些场景下我们需要确保某些操作即使在高并发的场景下也只会被执行一次,例如只加载一次配置文件等。
Go语言中的sync
包中提供了一个针对只执行一次场景的解决方案——sync.Once
,sync.Once
只有一个Do
方法,其签名如下:
func (o *Once) Do(f func())
注意:如果要执行的函数f
需要传递参数就需要搭配闭包来使用。
var icons map[string]image.Image var loadIconsOnce sync.Once func loadIcons() { icons = map[string]image.Image{ "left": loadIcon("left.png"), "up": loadIcon("up.png"), "right": loadIcon("right.png"), "down": loadIcon("down.png"), } } // Icon 是并发安全的 func Icon(name string) image.Image { loadIconsOnce.Do(loadIcons) return icons[name] }
并发安全的单例模式
package singleton import ( "sync" ) type singleton struct {} var instance *singleton var once sync.Once func GetInstance() *singleton { once.Do(func() { instance = &singleton{} }) return instance }
sync.Once
其实内部包含一个互斥锁和一个布尔值,互斥锁保证布尔值和数据的安全,而布尔值用来记录初始化是否完成。这样设计就能保证初始化操作的时候是并发安全的并且初始化操作也不会被执行多次。
3、sync.Map
Go 语言中内置的 map 不是并发安全的,请看下面这段示例代码。
package main import ( "fmt" "strconv" "sync" ) var m = make(map[string]int) func get(key string) int { return m[key] } func set(key string, value int) { m[key] = value } func main() { wg := sync.WaitGroup{} for i := 0; i < 10; i++ { wg.Add(1) go func(n int) { key := strconv.Itoa(n) set(key, n) fmt.Printf("k=:%v,v:=%v\n", key, get(key)) wg.Done() }(i) } wg.Wait() }
将上面的代码编译后执行,会报出fatal error: concurrent map writes
错误。我们不能在多个 goroutine 中并发对内置的 map 进行读写操作,否则会存在数据竞争问题。
像这种场景下就需要为 map 加锁来保证并发的安全性了,Go语言的sync
包中提供了一个开箱即用的并发安全版 map——sync.Map
。开箱即用表示其不用像内置的 map 一样使用 make 函数初始化就能直接使用。同时sync.Map
内置了诸如Store
、Load
、LoadOrStore
、Delete
、Range
等操作方法。
方法名 | 功能 |
---|---|
func (m *Map) Store(key, value interface{}) | 存储key-value数据 |
func (m *Map) Load(key interface{}) (value interface{}, ok bool) | 查询key对应的value |
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) | 查询或存储key对应的value |
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool) | 查询并删除key |
func (m *Map) Delete(key interface{}) | 删除key |
func (m *Map) Range(f func(key, value interface{}) bool) | 对map中的每个key-value依次调用f |
package main import ( "fmt" "strconv" "sync" ) // 并发安全的map var m = sync.Map{} func main() { wg := sync.WaitGroup{} // 对m执行20个并发的读写操作 for i := 0; i < 20; i++ { wg.Add(1) go func(n int) { key := strconv.Itoa(n) m.Store(key, n) // 存储key-value value, _ := m.Load(key) // 根据key取值 fmt.Printf("k=:%v,v:=%v\n", key, value) wg.Done() }(i) } wg.Wait() }