# 09.3 优雅地结束goroutines

本节内容将介绍如何使用go标准库中的sync包来解决上一节提到的Goroutine中的任务还未执行完成，main()函数就提前结束的问题。 本节的代码文件为syncGo.go,我们基于上一节的create.go来扩展syncGo.go。

syncGo.go的第一部分代码如下：

```
package main 

import ( 
    "flag" 
    "fmt" 
    "sync" 
)
```

如上所示，我们不再需要time包，我们将使用sync包中的功能来等待所有的Goroutine执行完成。

在第10章“并发 - 高级主题”中，我们将会学习两种方式来对Goroutine进行超时处理。 syncGo.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的定义只不过是一个包含三个字段的结构体：

```
type WaitGroup struct { 
        noCopy noCopy 
        state1 [12]byte 
        sema   uint32 
    }
```

syncGo.go的输出将显示有关sync.WaitGroup变量工作方式的更多信息。

syncGo.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()函数，以减少相同的计数器。

syncGo.go的最后一部分代码如下：

```
fmt.Printf("%#v\n", waitGroup) 
    waitGroup.Wait() 
    fmt.Println("\nExiting...") 
}
```

sync.Wait()调用将阻塞，直到sync.WaitGroup变量中的计数器为零，从而保证所有Goroutine能执行完成。

syncGo.go的输出如下：

```
$ 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()调用而增加和减少。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wskdsgcf.gitbook.io/mastering-go-zh-cn/09.0/09.3.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
