Golang中为什么select的default会多执行一次
Golang中为什么select的default会多执行一次
package main
import "fmt"
import "time"
func main() {
ch := make(chan int, 1)
x := 0
time.AfterFunc(10*time.Second, func() {
fmt.Println("10 seconds over....")
ch <- 8
})
for {
select {
case n := <-ch:
fmt.Println(n, "is arriving")
fmt.Println("done")
return
default:
x += 3
time.Sleep(3 * time.Second)
fmt.Println(x, "time to wait")
}
}
}
3 time to wait 6 time to wait 9 time to wait 10 seconds over… 12 time to wait 8 is arriving done
我认为“12 time to wait”不应该被打印出来,并且‘8 is arriving’应该紧接在‘10 seconds over…’之后出现。
更多关于Golang中为什么select的default会多执行一次的实战教程也可以访问 https://www.itying.com/category-94-b0.html
请看这些语句的顺序:
x += 3
time.Sleep(3 * time.Second)
fmt.Println(x, "time to wait")
它首先增加 x,然后休眠,接着打印“time to wait”消息。
当程序启动时(在 0 秒时刻),它开始休眠(从 0 秒到 3 秒),然后打印 "3 time to wait"。
之后,它再次休眠(从 3 秒到 6 秒),并打印 6 time to wait。
消息 "12 time to wait" 是在从 9 秒休眠到 12 秒后打印的。这就是为什么它会在 "10 seconds over...." 时间之前进入休眠状态。
这是一个典型的select调度时机问题。default分支在通道操作不可用时执行,但执行期间通道可能变为可读状态。
在你的代码中:
- 第3次循环(x=9)时,default分支开始执行
- 执行到
time.Sleep(3 * time.Second)时,定时器触发,通道被写入值8 - 但此时default分支仍在执行中,必须完成整个default代码块
- 所以会先打印"12 time to wait",然后进入下一次循环的select时才会读取通道
可以通过以下方式验证:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 1)
x := 0
time.AfterFunc(10*time.Second, func() {
fmt.Println("10 seconds over....")
ch <- 8
fmt.Println("Value sent to channel at:", time.Now())
})
for {
select {
case n := <-ch:
fmt.Println(n, "is arriving at:", time.Now())
fmt.Println("done")
return
default:
start := time.Now()
x += 3
time.Sleep(3 * time.Second)
fmt.Printf("x=%d, default执行时间: %v - %v\n",
x, start.Format("15:04:05.000"),
time.Now().Format("15:04:05.000"))
}
}
}
输出会显示:
x=3, default执行时间: 20:30:00.000 - 20:30:03.000
x=6, default执行时间: 20:30:03.000 - 20:30:06.000
x=9, default执行时间: 20:30:06.000 - 20:30:09.000
10 seconds over....
Value sent to channel at: 20:30:10.000
x=12, default执行时间: 20:30:09.000 - 20:30:12.000
8 is arriving at: 20:30:12.000
done
可以看到通道在第10秒被写入,但default分支从第9秒开始执行到第12秒,必须执行完成后才能进入下一次select检查通道。
要解决这个问题,可以缩短default中的睡眠时间,或者使用非阻塞的通道检查:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 1)
x := 0
time.AfterFunc(10*time.Second, func() {
fmt.Println("10 seconds over....")
ch <- 8
})
for {
select {
case n := <-ch:
fmt.Println(n, "is arriving")
fmt.Println("done")
return
default:
x += 3
// 改为多次短暂睡眠,以便及时响应通道
for i := 0; i < 3; i++ {
select {
case n := <-ch:
fmt.Println(n, "is arriving")
fmt.Println("done")
return
default:
time.Sleep(1 * time.Second)
fmt.Println(x, "time to wait")
}
}
}
}
}
这样就能在通道就绪时立即响应,不会出现多余的输出。

