先看一个例子:
package main
import "sync"
func main() {
var wg sync.WaitGroup
foo := make(chan int)
bar := make(chan int)
wg.Add(1)
go func() {
defer wg.Done()
select {
case foo <- <-bar:
default:
println("default")
}
}()
wg.Wait()
}
运行结果:
结果并不是执行 default 分支 ,而是死锁了
原因最后分析,接着看下一个例子:
package main
import (
"fmt"
"time"
)
func talk(msg string, sleep int) <-chan string {
ch := make(chan string)
go func() {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(sleep) * time.Millisecond)
}
}()
return ch
}
func fanIn(input1, input2 <-chan string) <-chan string {
ch := make(chan string)
go func() {
for {
select {
case ch <- <-input1:
case ch <- <-input2:
}
}
}()
return ch
}
func main() {
ch := fanIn(talk("A", 10), talk("B", 1000))
for i := 0; i < 10; i++ {
fmt.Printf("%q\n", <-ch)
}
}
运行结果:
发现还是死锁,每次都直接输出5个后死锁,同时多运行几次每次输出的5个结果也不相同。
如果将代码中select代码块改为下面这样就一切正常:
select {
case t := <-input1:
ch <- t
case t := <-input2:
ch <- t
}
对于 select 语句,在进入该语句时,会按源码的顺序对每一个 case 子句进行求值:这个求值只针对发送或接收操作的额外表达式。
在没有选择某个具体 case 执行前,例子中的 getVal()
、<-input
和 getch()
会执行。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
select {
case ch <- getVal(1):
fmt.Println("in first case")
case ch <- getVal(2):
fmt.Println("in second case")
default:
fmt.Println("default")
}
}()
fmt.Println("The val:", <-ch)
}
func getVal(i int) int {
fmt.Println("getVal, i=", i)
return i
}
执行结果:
从上面的结果可以看出在没有选择某个具体 case 执行前,,getVal()
都会按照源码顺序执行。
即使将上面的getVal方法加上sleep也不影响结果
func getVal(i int) int {
fmt.Println("getVal, i=", i)
time.Sleep(10000000)
return i
}
结果:
现在回到上面的那个问题 :
select {
case ch <- <-input1:
case ch <- <-input2:
}
结果分析:在select随机选择一个case执行前,<-input1
和 <-input2
都会执行,而且是往同一个ch中进行输入,相应的值是:A x 和 B x(其中 x 是 0-5)。但每次 select 只会选择其中一个 case 执行,所以 <-input1
和 <-input2
的结果,必然有一个被丢弃了,也就是不会被写入 ch 中。因此,一共只会输出 5 次,另外 5 次结果丢掉了。(你会发现,输出的 5 次结果中,x 比如是 0 1 2 3 4)
而 main 中循环 10 次,只获得 5 次结果,所以输出 5 次后,报死锁。
而改为下面的之所以正常是因为虽然select随机选择一个case执行前,<-input1
和 <-input2
都会执行,但是它们都重新申请了各自的变量,将结果存在各自的变量中,并不是对同一个chan输入,所以不会出现丢弃的情况,因为不会出现思索。
select {
case t := <-input1:
ch <- t
case t := <-input2:
ch <- t
}