学习播客:https://studygolang.com/articles/5224
package main
import (
"fmt"
"runtime"
"time"
)
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
func main() {
ch := make(chan int, 1024)
go func(ch chan int) {
for {
val := <-ch
fmt.Printf("val:%d\n", val)
}
}(ch)
tick := time.NewTicker(1 * time.Second)
for i := 0; i < 20; i++ {
select {
case ch <- i:
case <-tick.C:
fmt.Printf("%d: case <-tick.C\n", i)
}
time.Sleep(200 * time.Millisecond)
}
close(ch)
tick.Stop()
}
问题出在这个select里面:
select {
case ch <- i:
case <-tick.C:
fmt.Printf("%d: case <-tick.C\n", i)
}
当两个case条件都满足时,系统会通过一个伪随机算法决定哪个case将被执行
所以当 tick.C 条件满足的那个循环,有概率造成 ch<-i 没有发送
(虽然通道两端没有阻塞,满足发送条件)
个人解析:
for i := 0; i < 20; i++ {
select {
// 本质:双case同时发生时,且随机算法执行了第二个case,导致少写了一个i
case ch <- i:
case <-tick.C:
fmt.Printf("%d: case <-tick.C\n", i)
}
time.Sleep(200 * time.Millisecond)
}
这个例子的简化如下:
单case场景:
for i := 0; i < 20; i++ {
select {
case ch <- i:
fmt.Printf("%d: case <-tick.C\n", i)
}
time.Sleep(200 * time.Millisecond)
}
等价于:
for i := 0; i < 20; i++ {
ch <- i
fmt.Printf("%d: case <-tick.C\n", i)
time.Sleep(200 * time.Millisecond)
}
注意,这里的第一个case铁定发生
双case场景:
for i := 0; i < 20; i++ {
select {
case ch <- i:
case <-tick.C:
fmt.Printf("%d: case <-tick.C\n", i)
}
time.Sleep(200 * time.Millisecond)
}
不言而喻,要么第一个case发生,要么两个case都发生,基本不会出现第二个case单独出现的场景
因为第一个case每次select循环都满足条件,哪怕不延时
解决方案1:
一旦 tick.C 的 case 被随机到,就多执行一次 ch<-i (多个 case 不通用)
select {
case ch <- i:
case <-tick.C:
fmt.Printf("%d: case <-tick.C\n", i)
ch <- i
}
解决方案2:
将 tick.C 的 case 单独放到一个 select 里,并加入一个default(保证不阻塞)
select {
case ch <- i:
}
select {
case <-tick.C:
fmt.Printf("%d: case <-tick.C\n", i)
//一定要加,不然如果该 for 循环中的所有 case 都不满足,则该 select 阻塞,直到有一个 case 满足条件
default:
}
两种解决方案的输出都是希望的结果