每期一个小窍门: go 中 不确定的select语句

关于 select 语句

  • select 语句中,case不依赖代码书写顺序。
  • 如果case中有1个有消息时,其他case/default则不会执行。
  • 如果case中有多个消息时,随机任选1个进行执行,其他不会执行
  • 如果所有case都没有消息时,同时含有defalut分支,则会走default分支
  • 如果所有case都没有消息时,没有default分支,则会阻塞等待case中返回消息继续执行
func g1(ch chan int) {
    
    
	ch <- 42
}

func g2(ch chan int) {
    
    
	ch <- 43
}

func main() {
    
    

	ch1 := make(chan int)
	ch2 := make(chan int)

	go g1(ch1)
	go g2(ch2)

	select {
    
    
	case v1 := <-ch1:
		fmt.Println("Got:";, v1)
	case v2 := <-ch2:
		fmt.Println("Got", v2)
	default:
		fmt.Println("The default case!")
	}
}

由于 goroutine 的执行顺序不确定,所以这里也不确定到底是该输出 42 43 还是 The default case!,可以用下面的脚本执行 5000 次看看(大家把下面的脚本保存成 for.sh,然后 chmod a+x for.sh,接着 ./for.sh)

rm -f for.txt
touch for.txt
for i in `seq 5000`
do
  go build -race main.go
  ./main >> for.txt
  rm -f main
done

然后到 for.txt中看输出,大部分都是 default,很小部分是 42 和 43。所以他还是会优先的选择 case 去执行,只是我们不要写这种 select,一旦写了,我们大抵要为这种不确定性付出代价

那么为什么这位兄台的代码一定是 default呢?

func AsyncServices() chan int {
    
    
	ch := make(chan int, 1)
	go func() {
    
    
		time.Sleep(time.Millisecond * 200) // 不管有没有这个走的都是default
		ch <- 1
	}()
	return ch
}

func TestSelect(t *testing.T) {
    
    
	select {
    
    
	case ch := <-AsyncServices():
		t.Log(ch)
	case <-time.After(time.Millisecond * 100):
		t.Error("Time out.")
	default:
		t.Log("Receive nothing.")
	}
}

这个 select 能够确定每个 case 到底怎么执行,大抵上,AsyncServices() 这个函数调用是耗时的,且一定会比 default 慢,如果我们把上述代码改成下面这样子:

这样

func main() {
    
    
	ch := make(chan int, 1)
	go func() {
    
    
		ch <- 1
	}()
	select {
    
    
	case ch := <-ch:
		fmt.Println(ch)
	case <-time.After(time.Millisecond * 100):
		fmt.Println("Time out";)
	default:
		fmt.Println("Receive nothing")
	}
}

或者这样

func Test_ab(t *testing.T) {
    
    
	ch := make(chan int)

	go func() {
    
    
		ch <- 1
	}()
	
	time.Sleep(1000)
	select {
    
    
	case res1 := <-ch:
		fmt.Println("case1: ", res1)
	//case <-time.After(time.Millisecond * 100):
	//	fmt.Println("timeout")
	//case res2 := <-ch2:
	//	fmt.Println("case2: ", res2)
	default:
		fmt.Println("default: ")
	}
}

本质上都是在代码进入select 之前, 尽可能让通道返回数据

注意: select 精髓就在于不确定性, 如果强行固定到某个case, 那请想一想为什么要使用select?

猜你喜欢

转载自blog.csdn.net/qq_33709508/article/details/132309213
今日推荐