執行一個簡單協程
package main
import (
"fmt"
"time"
)
func main(){
for i := 0; i < 100 ; i++{
go fmt.Println(i)
}
time.Sleep(time.Second)
}
分析:
為什么會有sleep 呢, 主線程為了等待goroutine都運行完畢, 不得不在程序的末尾使用time.Sleep()來睡眠一段時間, 等待 其他線程充分運行 。對於簡單的代碼100 個for 循環可以在1秒內運行完但是實際的場景中大部分是1秒不夠的, 而且大部分的時間 我們都無法預測for 循環內的代碼運行時間的長短, 這個時候就不能使用time.sleep() 來完成等待操作了。
使用管道來 完成 操作
func main() {
c := make(chan bool, 100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
c <- true
}(i)
}
for i := 0; i < 100; i++ {
<-c
}
}
通道是可以完全達到目的的
但是在這里 管道會 有些大材小用,因為它被設計出來不僅僅只是在這里用作簡單的同步處理,在這里使用管道實際上是不合適的。而且假設我們有一萬、十萬甚至更多的for循環,也要申請同樣數量大小的管道出來,對內存也是不小的開銷
對於這種情況, go 語言中有一個其他的工具 sync.WaitGroup 能更加方便的達到這個目的。
WaitGroup對象內部有個計時器, 最初從0 開始, 他有3個方法 Add() , Done(), Wait()用來控制計數器的數量。 Add(n) 把計數器設置成n, Done() 每次把計數器-1, wait() 會阻塞代碼的運行, 直到計數器的值減為0
將上面代碼修改:
func main() {
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
這里計數器設置為 100 每個for 循環運行完畢都把計數器減1 主函數中使用Wait() 一直阻塞, 直到wg為0 也就是所有的 100 個for 循環都運行完畢, 相對於使用 管道來說 WaitGroup輕巧了很多。
注意的事項 使用waitGroup
計數器不能為負
我們不能使用Add()給wg 設置一個負值 否則代碼會報錯
panic: sync: negative WaitGroup counter
goroutine 1 [running]:
sync.(*WaitGroup).Add(0xc042008230, 0xffffffffffffff9c)
D:/Go/src/sync/waitgroup.go:75 +0x1d0
main.main()
D:/code/go/src/test-src/2-Package/sync/waitgroup/main.go:10 +0x54
同樣使用done() 也要特別注意不能把計數器的值設置為負值
WaitGroup 對象不是一個引用類型, 在通過函數傳值的時候需要使用地址;
注意:值類型包括基本數據類型,int,float,bool,string,以及數組和結構體(struct)。注意:指針也是值類型;sync.WaitGroup 對象是值類型,不是一個引用類型
值類型變量聲明后,不管是否已經賦值,編譯器為其分配內存,此時該值存儲於棧上。
func main() {
wg := sync.WaitGroup{} //所以這句可以換成 var wg sync.WaitGroup;
wg.Add(100)
for i := 0; i < 100; i++ {
go f(i, &wg)
}
wg.Wait()
}
// 一定要通過指針傳值,不然進程會進入死鎖狀態
func f(i int, wg *sync.WaitGroup) {
fmt.Println(i)
wg.Done()
}
原文鏈接:https://blog.csdn.net/yangxiaodong88/article/details/96309601