1. 从一个问题说起
代码语言:javascript复制package main
import (
"fmt"
)
var ch0 = make(chan int)
var ch1 = make(chan int)
var chs = [](chan int){ch0, ch1}
func getCh(i int) chan int{
fmt.Printf("get ch:%dn", i)
return chs[i]
}
func getNum(i int) int{
fmt.Printf("get num:%dn", i)
return i
}
func main(){
select {
case getCh(0)<- getNum(0):
fmt.Println("case 0")
case getCh(1)<- getNum(1):
fmt.Println("case 1")
default:
fmt.Println("default")
}
}
上面这段代码输出是什么?
如果你的答案是case 0, case 1随机出现,那么,请接着往下看。
2. 答案
题目的输出是这样的
代码语言:javascript复制get ch:0
get num:0
get ch:1
get num:1
default
题目涉及两个知识点:
- 对于无缓冲的channel,如果接收方未准备好,则发送操作将会被阻塞。因此上面的代码才会走到default分支。
- select中,所有case中的语句会被求值。这也是为什么明明走到了default,但getCh(0), getCh(1), getNum(0), getNum(1), 都会被执行。下一小节中我们会着重阐述这个问题。
3. select语句中的求值
手册中的说明是这样的:
For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the “select” statement. (更多详情点击这里)
这段话,被好多文章翻译为:
所有channel表达式都会被求值, 所有被发送的表达式都会被求值。求值顺序:自上而下、从左到右。
这…这是欺负我不懂英文么…如此翻译,隐去了太多细节!要想理解这段话,我们用下图来对齐下概念:
需要说明的是,receive operation可以只是<-ch
。
如此一来,这段话就行好理解了。对于select语句中的所有case,图中1,2的ch部分和3的expression部分都会被进行一次求值。求值顺序为代码顺序。
其重点在于,无论相应的case是被选中,求值都会被执行!正如手册中所说
Any side effects in that evaluation will occur irrespective of which (if any) communication operation is selected to proceed.
至此,相应你应该可以明白getCh(), getNum()输出的原因了。
4. 更进一步
如果确认了解了上面的知识点,我们来看下面的代码,输出是什么?
代码语言:javascript复制package main
import (
"fmt"
)
var ch1 = make(chan int)
var ch2 = make(chan int)
func main(){
select {
case ch1 <- <-ch2:
fmt.Println("case 0")
default:
fmt.Println("default")
}
}
答案是死锁了。
原因是这样的<-ch2
被作为发送语句ch1 <- <-ch2
的右值被整体求值。但<-ch2
本身是阻塞状态,无法求值,自然也无法进行select后面的执行步骤,因此死锁。这可能也是手册中所说的求值的副作用之一吧。
如果想解除死锁,简单修改下select部分即可。
代码语言:javascript复制 select {
case v:= <-ch2:
ch1 <- v
fmt.Println("case 0")
default:
fmt.Println("default")
}
推荐阅读:
- 手册Select_statements
- 手册Send statements
- 一个 select 死锁问题