1、Go語言并發(fā)之WaitGroup
goroutine 和 chan,一個用于并發(fā),另一個用于通信。沒有緩沖的通道具有同步的功能,除此之外,sync 包也提
供了多個 goroutine 同步的機制,主要是通過 WaitGroup 實現(xiàn)的。
WaitGroup 用來等待多個 goroutine 完成,main goroutine 調(diào)用 Add 設(shè)置需要等待 goroutine 的數(shù)目,每一個
goroutine 結(jié)束時調(diào)用 Done(),Wait() 被 main 用來等待所有的 goroutine 完成。
主要數(shù)據(jù)結(jié)構(gòu)和操作如下:
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 之間的協(xié)同工作。
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結(jié)束后給wg計數(shù)減1 ,wg.Done()等價于wg.Add(-1)
// defer wg.Add(-1)
defer wg.Done()
// 發(fā)送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 不加鎖
多線程中使用睡眠函數(shù)不優(yōu)雅,直接用 sync.WaitGroup 保證一個 goroutine 剛退出就可以繼續(xù)執(zhí)行,不需要自
己猜需要 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結(jié)束就登記-1
defer wg.Done()
for j := 0; j < 100000; j++ {
count++
}
}()
}
// 等待所有登記的goroutine都結(jié)束
wg.Wait()
// 346730
fmt.Print(count)
}
啟動十個goroutine對count自增10w次,理想狀況為100w,但由于沒有鎖,會出現(xiàn)實際情況遠遠小于并且不相等
的情況。為什么會出現(xiàn)這樣的結(jié)果?因為自增并不是一個原子操作,很可能幾個goroutine同時讀到同一個數(shù),自
增,又將同樣的數(shù)寫了回去。
1.2 互斥鎖
1.2.1 直接使用鎖
共享資源是count變量,臨界區(qū)是count++,臨界區(qū)之前加鎖,使其他goroutine在臨界區(qū)阻塞,離開臨界區(qū)解
鎖,就可以解決這個 data race 的問題。go語言是通過 sync.Mutex 實現(xiàn)這一功能。文章來源:http://www.zghlxwxcb.cn/news/detail-476077.html
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。文章來源地址http://www.zghlxwxcb.cn/news/detail-476077.html
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結(jié)束就登記-1
defer wg.Done()
for j := 0; j < 100000; j++ {
counter.Incr()
}
}()
}
// 等待所有登記的goroutine都結(jié)束
wg.Wait()
// 1000000
fmt.Print(counter.Count())
}
到了這里,關(guān)于Go語言并發(fā)之WaitGroup的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!