Files
the-way-to-go_ZH_CN/eBook/16.9.md

75 lines
2.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 16.9 闭包和协程的使用
请看下面代码:
```go
package main
import (
"fmt"
"time"
)
var values = [5]int{10, 11, 12, 13, 14}
func main() {
// 版本 A
for ix := range values { // ix 是索引值
func() {
fmt.Print(ix, " ")
}() // 调用闭包打印每个索引值
}
fmt.Println()
// 版本 B和 A 版本类似,但是通过调用闭包作为一个协程
for ix := range values {
go func() {
fmt.Print(ix, " ")
}()
}
fmt.Println()
time.Sleep(5e9)
// 版本 C正确的处理方式
for ix := range values {
go func(ix interface{}) {
fmt.Print(ix, " ")
}(ix)
}
fmt.Println()
time.Sleep(5e9)
// 版本 D输出值
for ix := range values {
val := values[ix]
go func() {
fmt.Print(val, " ")
}()
}
time.Sleep(1e9)
}
```
输出:
```
0 1 2 3 4
4 4 4 4 4
1 0 3 4 2
10 11 12 13 14
```
版本 A 调用闭包 5 次打印每个索引值,版本 B 也做相同的事,但是通过协程调用每个闭包。按理说这将执行得更快,因为闭包是并发执行的。如果我们阻塞足够多的时间,让所有协程执行完毕,版本 B 的输出是:`4 4 4 4 4`。为什么会这样?在版本 B 的循环中,`ix` 变量实际是一个单变量,表示每个数组元素的索引值。因为这些闭包都只绑定到一个变量,这是一个比较好的方式,当你运行这段代码时,你将看见每次循环都打印最后一个索引值 `4`,而不是每个元素的索引值。因为协程可能在循环结束后还没有开始执行,而此时 `ix` 值是 `4`
版本 C 的循环写法才是正确的:调用每个闭包时将 `ix` 作为参数传递给闭包。`ix` 在每次循环时都被重新赋值,并将每个协程的 `ix` 放置在栈中,所以当协程最终被执行时,每个索引值对协程都是可用的。注意这里的输出可能是 `0 2 1 3 4` 或者 `0 3 1 2 4` 或者其他类似的序列,这主要取决于每个协程何时开始被执行。
在版本 D 中,我们输出这个数组的值,为什么版本 B 不能而版本 D 可以呢?
因为版本 D 中的变量声明是在循环体内部,所以在每次循环时,这些变量相互之间是不共享的,所以这些变量可以单独的被每个闭包使用。
## 链接
- [目录](directory.md)
- 上一节:[误用协程和通道](16.8.md)
- 下一节:[糟糕的错误处理](16.10.md)