Die Falle liegt in der Aufrufreihenfolge der drei Funktionen von WaitGroup. Sehen wir uns zunächst die Funktionen der drei Funktionen an: sync.WaitGroup
是并发环境中,一个相当常用的数据结构,用来等待所有协程的结束,在写代码的时候都是按着例子的样子写的,也没用深究过它的使用。前几日想着能不能在协程中执行Add()
函数,答案是不能,这里介绍下。
陷阱在WaitGroup的3个函数的调用顺序上。先回顾下3个函数的功能:
-
Add(delta int)
:给计数器增加delta,比如启动1个协程就增加1。 -
Done()
:协程退出前执行,把计数器减1。 -
Wait()
:阻塞等待计数器为0。
考一考
下面的程序是创建了协程father,然后father协程创建了10个子协程,main函数等待所有协程结束后退出,看看下面代码有没有什么问题?
package main import ( "fmt" "sync" ) func father(wg *sync.WaitGroup) { wg.Add(1) defer wg.Done() fmt.Printf("father\n") for i := 0; i < 10; i++ { go child(wg, i) } } func child(wg *sync.WaitGroup, id int) { wg.Add(1) defer wg.Done() fmt.Printf("child [%d]\n", id) } func main() { var wg sync.WaitGroup go father(&wg) wg.Wait() log.Printf("main: father and all chindren exit") }
发现问题了吗?如果没有看下面的运行结果:main函数在子协程结束前就开始结束了。
father main: father and all chindren exit child [9] child [0] child [4] child [7] child [8]
陷阱分析
产生以上问题的原因在于,创建协程后在协程内才执行Add()
函数,而此时Wait()
函数可能已经在执行,甚至Wait()
函数在所有Add()
执行前就执行了,Wait()
-
Add(delta int)
: Delta zum Zähler hinzufügen, zum Beispiel um 1 erhöhen, wenn eine Coroutine gestartet wird. -
Done()
: Wird ausgeführt, bevor die Coroutine beendet wird, wodurch der Zähler um 1 verringert wird. -
Wait()
: Der blockierende Wartezähler ist 0.
package main import ( "fmt" "sync" ) func father(wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("father\n") for i := 0; i < 10; i++ { wg.Add(1) go child(wg, i) } } func child(wg *sync.WaitGroup, id int) { defer wg.Done() fmt.Printf("child [%d]\n", id) } func main() { var wg sync.WaitGroup wg.Add(1) go father(&wg) wg.Wait() fmt.Println("main: father and all chindren exit") }Haben Sie das Problem gefunden? Wenn Sie die folgenden Ausführungsergebnisse nicht sehen: Die Hauptfunktion beginnt zu enden, bevor die Unter-Coroutine endet.
father child [9] child [7] child [8] child [1] child [4] child [5] child [2] child [6] child [0] child [3] main: father and all chindren exitTrap-AnalyseDer Grund für das obige Problem ist, dass die Funktion
Add()
innerhalb der Coroutine ausgeführt wird, nachdem die Coroutine erstellt wurde, und zu diesem Zeitpunkt die Funktion Wait()-Funktion <p>Vielleicht</p> wird bereits ausgeführt, oder sogar die <code>Wait()
-Funktion wird ausgeführt, bevor alle Add()
ausgeführt werden, und zwar sofort, wenn Wait ()
wird ausgeführt. Es wird sichergestellt, dass der Zähler von WaitGroup 0 ist, Wait endet und das Hauptprogramm wird beendet. Infolgedessen wurden nicht alle Unter-Coroutinen vollständig beendet und die Hauptfunktion endet. Der richtige Ansatz