Go语言并发之WaitGroup的用法详解
作者:zsx_yiyiyi
1、Go语言并发之WaitGroup
goroutine 和 chan,一个用于并发,另一个用于通信。没有缓冲的通道具有同步的功能,除此之外,sync 包也提
供了多个 goroutine 同步的机制,主要是通过 WaitGroup 实现的。
WaitGroup 用来等待多个 goroutine 完成,main goroutine 调用 Add 设置需要等待 goroutine 的数目,每一个
goroutine 结束时调用 Done(),Wait() 被 main 用来等待所有的 goroutine 完成。
主要数据结构和操作如下:
type WaitGroup struct { // contains filtered or unexported fields } // 添加等待信号 func (wg*WaitGroup) Add (delta int) // 释放等待信号 func (wg*WaitGroup) Done() // 等待 func (wg*WaitGroup) Wait()
下面的程序演示如何使用 sync.WaitGroup 完成多个 goroutine 之间的协同工作。
package main import ( "net/http" "sync" ) var wg sync.WaitGroup var urls = []string{ "https://news.sina.com.cn/", "https://www.bilibili.com/", "https://www.qq.com/", } func main() { for _, url := range urls { //每一个url启动一个goroutine,同时给wg加1 wg.Add(1) // 启动一个goroutine获取URL go func(url string) { // 当前goroutine结束后给wg计数减1 ,wg.Done()等价于wg.Add(-1) // defer wg.Add(-1) defer wg.Done() // 发送http get请求并打印http返回码 resp, err := http.Get(url) if err == nil { println(resp.Status) } }(url) } // 等待所有HTTP获取完成 wg.Wait() }
# 输出
501 Not Implemented
200 OK
200 OK
1.1 不加锁
多线程中使用睡眠函数不优雅,直接用 sync.WaitGroup 保证一个 goroutine 刚退出就可以继续执行,不需要自
己猜需要 sleep 多久。
package main import ( "fmt" "sync" ) var wg sync.WaitGroup func main() { // 启动一个goroutine就登记+1,启动十个就+10 wg.Add(10) var count = 0 for i := 0; i < 10; i++ { go func() { // goroutine结束就登记-1 defer wg.Done() for j := 0; j < 100000; j++ { count++ } }() } // 等待所有登记的goroutine都结束 wg.Wait() // 346730 fmt.Print(count) }
启动十个goroutine对count自增10w次,理想状况为100w,但由于没有锁,会出现实际情况远远小于并且不相等
的情况。为什么会出现这样的结果?因为自增并不是一个原子操作,很可能几个goroutine同时读到同一个数,自
增,又将同样的数写了回去。
1.2 互斥锁
1.2.1 直接使用锁
共享资源是count变量,临界区是count++,临界区之前加锁,使其他goroutine在临界区阻塞,离开临界区解
锁,就可以解决这个 data race 的问题。go语言是通过 sync.Mutex 实现这一功能。
package main import ( "fmt" "sync" ) var wg sync.WaitGroup func main() { // 定义锁 var mu sync.Mutex wg.Add(10) var count = 0 for i := 0; i < 10; i++ { go func() { defer wg.Done() for j := 0; j < 100000; j++ { // 加锁 mu.Lock() count++ // 解锁 mu.Unlock() } }() } wg.Wait() // 1000000 fmt.Print(count) }
1.2.2 嵌入字段方式使用锁
把 Mutex 嵌入 struct,可以直接在这个 struct 上使用 Lock/Unlock。
package main import ( "fmt" "sync" ) var wg sync.WaitGroup type Counter struct { sync.Mutex Count uint64 } func main() { var counter Counter wg.Add(10) for i := 0; i < 10; i++ { go func() { defer wg.Done() for j := 0; j < 100000; j++ { counter.Lock() counter.Count++ counter.Unlock() } }() } wg.Wait() // 1000000 fmt.Print(counter.Count) }
1.2.3 把加/解锁封装成方法
package main import ( "fmt" "sync" ) var wg sync.WaitGroup type Counter struct { mu sync.Mutex count uint64 } func (c *Counter) Incr() { c.mu.Lock() c.count++ c.mu.Unlock() } func (c *Counter) Count() uint64 { c.mu.Lock() defer c.mu.Unlock() return c.count } func main() { var counter Counter // 启动一个goroutine就登记+1,启动十个就+10 wg.Add(10) for i := 0; i < 10; i++ { go func() { // goroutine结束就登记-1 defer wg.Done() for j := 0; j < 100000; j++ { counter.Incr() } }() } // 等待所有登记的goroutine都结束 wg.Wait() // 1000000 fmt.Print(counter.Count()) }
到此这篇关于Go语言并发之WaitGroup的用法详解的文章就介绍到这了,更多相关Go语言 WaitGroup内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!