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

3 回复

感谢您的回复。

更多关于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分支在通道操作不可用时执行,但执行期间通道可能变为可读状态。

在你的代码中:

  1. 第3次循环(x=9)时,default分支开始执行
  2. 执行到time.Sleep(3 * time.Second)时,定时器触发,通道被写入值8
  3. 但此时default分支仍在执行中,必须完成整个default代码块
  4. 所以会先打印"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")
				}
			}
		}
	}
}

这样就能在通道就绪时立即响应,不会出现多余的输出。

回到顶部