Go并發(fā)等待
上節(jié)答疑
上一節(jié)有讀者問goroutine stack size一般是多大,我進行了詳細的查詢
關于 goroutine stack size(棧內存大?。?官方的文檔 中所述,1.2 之前最小是4kb,在1.2 變成8kb,并且可以使用SetMaxStack 設置棧最大大小。
在 runtime/debug 包能控制最大的單個 goroutine 的堆棧的大小。在 64 位系統上默認為 1GB,在 32 位系統上默認為 250MB。
因為每個goroutine需要能夠運行,所以它們都有自己的棧。假如每個goroutine分配固定棧大小并且不能增長,太小則會導致溢出,太大又會浪費空間,無法存在許多的goroutine。
所以在1.3版本中,改為了 Contiguous stack( 連續(xù)棧 ),為了解決這個問題,goroutine可以初始時只給棧分配很小的空間(8KB),然后隨著使用過程中的需要自動地增長。這就是為什么Go可以開千千萬萬個goroutine而不會耗盡內存。
1.4 版本 goroutine 堆棧從 8Kb 減少到 2Kb
Golang并發(fā)等待
★本節(jié)源碼位置 https://github.com/golang-minibear2333/golang/blob/master/4.concurrent/goroutine-wait/
”
簡介
goroutine 是 Golang 中非常有用的功能,有時候 goroutine 沒執(zhí)行完函數就返回了,如果希望等待當前的 goroutine 執(zhí)行完成再接著往下執(zhí)行,該怎么辦?
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("hello world")
fmt.Println("over!")
}
輸出 over! , 主線程沒有等待
使用 Sleep 等待
func main() {
go say("hello world")
time.Sleep(time.Second*1)
fmt.Println("over!")
}
運行修改后的程序,結果如下:
hello world
hello world
hello world
over!
結果符合預期,但是太 low 了,我們不知道實際執(zhí)行中應該等待多長時間,所以不能接受這個方案!
發(fā)送信號
func main() {
done := make(chan bool)
go func() {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println("hello world")
}
done <- true
}()
<-done
fmt.Println("over!")
}
輸出的結果和上面相同,也符合預期
這種方式不能處理多個協程,所以也不是優(yōu)雅的解決方式。
WaitGroup
Golang 官方在 sync 包中提供了 WaitGroup 類型可以解決這個問題。其文檔描述如下:
使用方法可以總結為下面幾點:
在父協程中創(chuàng)建一個 WaitGroup實例,比如名稱為:wg調用 wg.Add(n),其中 n 是等待的goroutine的數量在每個 goroutine運行的函數中執(zhí)行defer wg.Done()調用 wg.Wait()阻塞主邏輯直到所有 goroutine執(zhí)行完成。
func main() {
var wg sync.WaitGroup
wg.Add(2)
go say2("hello", &wg)
go say2("world", &wg)
fmt.Println("over!")
wg.Wait()
}
func say2(s string, waitGroup *sync.WaitGroup) {
defer waitGroup.Done()
for i := 0; i < 3; i++ {
fmt.Println(s)
}
}
輸出,注意順序混亂是因為并發(fā)執(zhí)行
hello
hello
hello
over!
world
world
world
小心缺陷
簡短的例子,注意循環(huán)傳入的變量用中間變量替代,防止閉包 bug
func errFunc() {
var wg sync.WaitGroup
sList := []string{"a", "b"}
wg.Add(len(sList))
for _, d := range sList {
go func() {
defer wg.Done()
fmt.Println(d)
}()
}
wg.Wait()
}
輸出,可以發(fā)現全部變成了最后一個
b
b
父協程與子協程是并發(fā)的。父協程上的for循環(huán)瞬間執(zhí)行完了,內部的協程使用的是d最后的值,這就是閉包問題。
解決方法當作參數傳入
func correctFunc() {
var wg sync.WaitGroup
sList := []string{"a", "b"}
wg.Add(len(sList))
for _, d := range sList {
go func(str string) {
defer wg.Done()
fmt.Println(str)
}(d)
}
wg.Wait()
}
輸出
b
a
要留意 range 中的value有可能出現 1.7.3 有可能會遇到的坑!(位于電子書golang.coding3min.com中)
