ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# **优雅地结束goroutines** 本节内容将介绍如何使用Go标准库中的`sync`包来解决上一节提到的`goroutine`中的任务还未执行完成,`main()`函数就提前结束的问题。 本节的代码文件为syncGo.go,我们基于上一节的`create.go`来扩展`syncGo.go`。 `syncGo.go`的第一部分代码如下: ```go package main import ( "flag" "fmt" "sync" ) ``` 如上所示,我们不再需要time包,我们将使用sync包中的功能来等待所有的goroutine执行完成。 在第10章“并发 - 高级主题”中,我们将会学习两种方式来对goroutine进行超时处理。 第二部分代码如下: ```go func main() { n := flag.Int("n", 20, "Number of goroutines") flag.Parse() count := *n fmt.Printf("Going to create %d goroutines.\n", count) var waitGroup sync.WaitGroup ``` 在上面的代码中,我们定义了sync.WaitGroup类型的变量,查看sync包的源码我们可以发现,waitgroup.go文件位于sync目录中,sync.WaitGroup的定义只不过是一个包含三个字段的结构体: ```go type WaitGroup struct { noCopy noCopy state1 [12]byte sema uint32 } ``` `syncGo.go`的输出将显示有关`sync.WaitGroup`变量工作方式的更多信息。 第三部分代码如下: ```go fmt.Printf("%#v\n", waitGroup) for i := 0; i < count; i++ { waitGroup.Add(1) go func(x int) { defer waitGroup.Done() fmt.Printf("%d ", x) }(i) } ``` 在这里,你可以使用for循环创建所需数量的`goroutine`。(当然,也可以写多个顺序的Go语句。) 每次调用`sync.Add()`都会增加`sync.WaitGroup`变量中的计数器。需要注意的是,在go语句之前调用`sync.Add(1)`非常重要,以防止出现任何形式的竞争。当每个`goroutine`完成其工作时,将执行`sync.Done()`函数,以减少相同的计数器。 最后一部分代码如下: ```go fmt.Printf("%#v\n", waitGroup) waitGroup.Wait() fmt.Println("\nExiting...") } ``` `sync.Wait()`调用将阻塞主程序,直到`sync.WaitGroup`变量中的计数器为零,从而保证所有`goroutine`能执行完成。 `syncGo.go`的输出如下: ```bash $ go run syncGo.go Going to create 20 goroutines. sync.WaitGroup{noCopy:sync.noCopy{}, state1:[12]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, sema:0x0} sync.WaitGroup{noCopy:sync.noCopy{}, state1:[12]uint8{0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, sema:0x0} 19 7 8 9 10 11 12 13 14 15 16 17 0 1 2 5 18 4 6 3 Exiting... $ go run syncGo.go -n 30 Going to create 30 goroutines. sync.WaitGroup{noCopy:sync.noCopy{}, state1:[12]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, sema:0x0} 1 0 4 5 17 7 8 9 10 11 12 13 2 sync.WaitGroup{noCopy:sync.noCopy{}, state1:[12]uint8{0x0, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, sema:0x0} 29 15 6 27 24 25 16 22 14 23 18 26 3 19 20 28 21 Exiting... $ go run syncGo.go -n 30 Going to create 30 goroutines. sync.WaitGroup{noCopy:sync.noCopy{}, state1:[12]uint8{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, sema:0x0} sync.WaitGroup{noCopy:sync.noCopy{}, state1:[12]uint8{0x0, 0x0, 0x0, 0x0, 0x1e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, sema:0x0} 29 1 7 8 2 9 10 11 12 4 13 15 0 6 5 22 25 23 16 28 26 20 19 24 21 14 3 17 18 27 Exiting... ``` `syncGo.go`的输出因执行情况而异。另外,当`goroutines`的数量为30时,一些`goroutine`可能会在第二个`fmt.Printf(“%#v \ n”,waitGroup)`语句之前完成它们的工作。最后需要注意`sync.WaitGroup`中的`state1`字段是一个保存计数器的元素,该计数器根据`sync.Add()`和`sync.Done()`调用而增加和减少。