> For the complete documentation index, see [llms.txt](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/10.0/10.2.md).

# 10.2 select关键字

您很快就会了解到，`select` 关键字非常强大，它可以在各种情况下做很多事情。Go中的 `select` 语句看起来像 channels 的 `switch` 语句。实际上，这意味着 `select` 允许 goroutine 等待多个通信操作。因此，您从 `select` 获得的主要好处就是它使您能够使用一个`select` 块处理多个 channels。因此，您可以在 channels 上进行非阻塞操作。

> 使用多 channels 和 `select` 关键字的最大问题是 **死锁**。就是说为了避免此类死锁，您在设计和开发过程中要格外的小心。

`select.go` 的代码将阐明 `select` 关键字的用法。这个程序分五部分来介绍。

`select.go` 的第一部分展示如下代码：

```go
package main

import(
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "time"
)
```

来自 `select.go` 的第二部分代码如下：

```go
func gen(min, max int, createNumber chan int, end chan bool) {
    for {
        select {
            case createNumber <- rand.Intn(max-min) + min:
            case <- end:
                close(end)
                return
            case <- time.After(4 * time.Second):
                fmt.Println("\ntime.After()!")
        }
    }
}
```

那么，在 `select` 块中的代码真正发生了什么？这个特别的 `select` 语句有三种情况。注意 `select` 语句不需要 `default` 分支。您可以把这个 `select` 语句的第三个分支当作 `default` 分支。`time.After()` 函数在指定时间过后返回，因此它将在其他 channels 被阻塞时解锁 `select` 语句。

`select` 语句不是按顺序计算的，因为所有的 channels 都是同时检查的。如果在 `select` 语句中没有 channels 是准备好的，那么 `select` 语句就会阻塞，直到有一个 channels 准备好。如果 `select` 语句中有多个 channels 准备好，那么 Go 运行时就会在这些准备好的 channles 中随机选择一个。Go 运行时在这些准备好的 channels 之间做随机选择时尽量做到一致和公平。

`select.go` 的第三部分如下：

```go
func main() {
    rand.Seed(time.Now().Unix())
    createNumber := make(chan int)
    end := make(chan bool)

    if len(os.Args) != 2 {
        fmt.Println("Please give me an integer!")
        return
    }
```

`select.go` 程序的第四部分包含如下代码：

```go
    n, _ := strconv.Atoi(os.Args[1])
    fmt.Printf("Going to create %d random numbers.\n", n)
    go gen(0, 2*n, createNumber, end)

    for i := 0; i < n; i++ {
        fmt.Printf("%d ", <-createNumber)
    }
```

> *没有检查* `strconv.Atoi()` *返回的错误值是为节省一些空间。您在真实应用中不应该这样做*

`select.go` 程序的剩余代码如下：

```go
    time.Sleep(5 * time.Second)
    fmt.Println("Exting...")
    end <- true
}
```

`time.Sleep(5 * time.Second)` 语句的主要目地是给 `gen()` 中的 `time.After()` 函数足够的时间返回，从而激活 `select` 语句中的相关分支。

`main()` 函数最后的一条语句是通过激活在 `gen()` 里的 `select` 语句中的 `case <-end` 分支来终止程序并执行相关代码。

执行 `select.go` 将产生如下输出：

```
$go run select.go 10
Going to create 10 random nubmers.
12 17 8 14 19 9 2 0 19 5
time.After()!
Exiting...
```

> `select` *最大优点就是它可以连接，编排和管理多个 channels。当 channels 连接 goroutines 时，*`select` *连接那些连接 goroutines 的 channels。因此，如果* `select` *语句不是 Go 并发模型中唯一重要的部分，也是之一。*
