go中的select提供了一种方式来处理多个channels,select语句满足如下条件
- 每个channel的值都会被计算
- 如果没有channel有产出的话,会阻塞直到一个channel产出
- 如果多个channel有产出的话,会假随机(pseudo-randomly)的选择一个产出
- 在有default语句的情况下,没有channel准备好的时候,会立即执行default的语句
下面有一个实例,源码中对boring返回结果的注释(第一行末尾的注释)直译是返回一个只接受string类型的channel。但是个人觉得翻译为返回的是一个只读的string类型的channel更好,因为后续的对c的操作,都只能从c中读取string。
func boring(msg string) <-chan string { // Returns receive-only channel of strings.
c := make(chan string)
go func() { // We launch the goroutine from inside the function.
for i := 0; ; i++ {
c <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
}
}()
return c // Return the channel to the caller.
}
func fanIn(input1, input2 <-chan string) <-chan string {
c := make(chan string)
go func() {
for {
select {
case s := <-input1: c <- s
case s := <-input2: c <- s
}
}
}()
return c
}
func main() {
joe := boring("Joe")
ann := boring("Ann")
c := fallin(joe, ann)
for i := 0; i < 20; i++ {
fmt.Println(<-c)
}
fmt.Println("You're both boring; I'm leaving.")
}
产生的结果如下,其中由于每个boring中的sleep的时间不确定,所以导致结果是Joe最终对应的i为10,而Ann最终对应的i为8
Joe 0
Ann 0
Joe 1
Ann 1
Joe 2
Ann 2
Joe 3
Ann 3
Joe 4
Ann 4
Joe 5
Ann 5
Joe 6
Ann 6
Ann 7
Joe 7
Joe 8
Joe 9
Ann 8
Joe 10
You're both boring; I'm leaving.
select语句满足的条件1是值得思考的,每个channel都是需要计算取值的,因为没有正确的理解这句话是可能犯错误的。
可以思考下面这个函数的输出,三秒之后函数会停止执行吗?
func main() {
c := boring("Joe")
for {
select {
case s := <-c:
fmt.Println(s)
case <- time.After(3 * time.Second):
fmt.Println("Your're too slow")
return
}
}
}
答案是不会的,因为每次执行到select语句中的各个选项的时候,都会计算每个case中的值。相当于在第一次输出c的产出之后,第二次进入select的选择的时候,time.After()就又重新开始计时了。要想在三秒之后停止这个函数,需要调整time.After的位置到select执行之前。
func main() {
c := boring("Joe")
timeout := time.After(3 * time.Second)
for {
select {
case s := <-c:
fmt.Println(s)
case <- timeout:
fmt.Println("Your're too slow")
return
}
}
}
本文内容翻译自 Go Concurrency Patterns
做一个知识型讨饭的,有些闲钱的,觉得对你有用的可以捐点