Golang: Language: Concurrency: Select

 13th October 2021 at 3:41pm

select 会 block 到指定的一批 channel ready;如果多个 channel ready,会随机选择其中一个。写法很像 switch

select {
// 可以是 receive from channel
case s := <-output:
    fmt.Println(s1)
// 也可以是 send to channel
case c <- "hello":
    // do nothing
case <-done:
    fmt.Println("Done")
}

可以有 default 分支。效果是如果没有 channel ready,就会走 default

default:
    fmt.Println("None of the other case is ready")

Patterns

select 可以用来表示这一种场景:把一个消息发送到某一 channel;如果无法发送(比如 channel 缓冲区满),则无视。

select {
case c.errors <- err:
default:
	// if c.errors is full, do nothing
}

select 经常跟 for 配合,用来实现超时或者退出功能。

实现超时功能(来自 这里):

func main() {
    c := boring("Joe")
    for {
        select {
        case s := <-c:
            fmt.Println(s)
        case <-time.After(1 * time.Second):
            fmt.Println("You're too slow.")
            return
        }
    }
}

注意上面的 time.After(1 * time.Second) 是在每次执行 select 语句时都会 evaluate 的,意味着 for 循环中的每次 select 都会有 1s 超时时间。如果你想设置整个 for 循环的超时时间,这样写:

func main() {
    c := boring("Joe")
    timeout := time.After(5 * time.Second)
    for {
        select {
        case s := <-c:
            fmt.Println(s)
        case <-timeout:
            fmt.Println("You talk too much.")
            return
        }
    }
}

读数据,当读完时执行清理工作(并不实用):

readResult:
for {
    select {
    case s <- result:
        fmt.Println(s)
    case <-done:
        cleanUp()
        break readResult
    }
}

因为 break 默认退出的是最内层的 for / switch / select 语句,因此想 break 掉 for 循环时,需要配合 label(readResult:)使用。

不是很推荐这样写。因为当 resultdone 同时 ready 时,如果 go 随机选了 done 分支来执行,result 中的值被漏处理。我认为如果向 result 发送完值后,将其 close,再用 for 循环去遍历 result 更合理。