一、Sync包的WaitGroup方法
有时候我们在生硬的使用time.sleep方法,肯定不合适。代码显得不够优雅。而go语言提供了waitGroup方法,使并发任务的同步。
我们先来看一段代码:
var wg sync.WaitGroup
func hello() {
defer wg.Done()
fmt.Println("Hello Goroutine!")
}
func main() {
wg.Add(1)
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
wg.Wait()
}
提供了三个方法:
(wg *waitGroup)Add(delta int) 每增加一个groutine需要计数器增加1
(wg *waitGroup)Done() 每一个groutine执行完需要调用,计数器进行减1
(wg *waitGroup)Wait() 等待所有的并发任务完成,阻塞到计数器变为0
底层WaitGroup 维护着一个计数器,可以进行增加和减少。加入当我们开启了n个并发任务,计数器就要增加到n.每一个任务完成调用Done方法进行减1.通过调用wait方法等待任务完成。当计数器减少到0,表示所有任务完成。
二、Sync的once方法介绍
在编程的很多场景下我们需要保证某些操作在高并发场景下只进行一次操作。例如加载配置、关闭一次通道。Go语言的提供了一次场景操作的解决方法之-Sync.Once。
func (o *Once) Do(f func()) {}
提供一次加载配置代码:
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]
}
SyncOnce底层是一个互斥锁➕一个布尔值实现的,互斥锁是保证bool值和数据安全,bool值是记录操作完成只被执行一次。这样就可以保证操作被执行数据安全并且被执行一次。
三、Sync.Map
Go语言中内置的map不是并发安全的。请看下面的示例:
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 < 20; 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()
}
上面的代码开启少量几个goroutine的时候可能没什么问题,当并发多了之后执行上面的代码就会报fatal error: concurrent map writes错误。
像这种场景下就需要为map加锁来保证并发的安全性了,Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。
我们来看下面代码展示:
var m = sync.Map{}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 20; i++ {
wg.Add(1)
go func(n int) {
key := strconv.Itoa(n)
m.Store(key, n)
value, _ := m.Load(key)
fmt.Printf("k=:%v,v:=%v\n", key, value)
wg.Done()
}(i)
}
wg.Wait()
}