Golang中select的default分支为何每次都会执行三次,即使channel已返回

Golang中select的default分支为何每次都会执行三次,即使channel已返回

import "fmt"
import "time"

func ab(ch chan bool,i int) {
	duration := time.After(time.Second * time.Duration(600))
	for {
		select {
		case <- duration:
			fmt.Printf(" received value: %d ",i)
			break
		default:
			fmt.Printf(" no value received %d ",i)
			ch <- true
			break
		}
	}
}

func main() {
	for i := 1; i < 10; i++{
		fmt.Printf("Testcase %d start",i)
		ch := make(chan bool, 1)
		go ab(ch,i)
		if <- ch {
			fmt.Printf("Testcase %d end",i)
		}
	fmt.Printf("sleeping for 10 seconds")
	time.Sleep(10*time.Second)
	}
}

更多关于Golang中select的default分支为何每次都会执行三次,即使channel已返回的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

有人能解释为什么它会打印三次而不是一次吗

更多关于Golang中select的default分支为何每次都会执行三次,即使channel已返回的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的回复,我现在明白了,这里使用 return 而不是 break 是有效的

因为你使用了带缓冲的通道。

它可以容纳1个元素。第一次触发时它会打印然后发送。这个元素在主goroutine中立即被读取。此时通道再次变为空闲状态。

循环第二次运行时再次打印并发送。现在没有从通道读取的操作,所以通道已满。

现在进行第三次迭代。同样会发生打印,然后ab-goroutine会因为通道已满而阻塞。

你可能想要的是从fir循环中跳出而不是从select中跳出。你需要给for循环添加标签并使用带标签的break来实现这一点

在您的代码中,default分支每次循环都会执行,因为select语句会立即检查所有case的条件,如果没有任何case准备就绪(例如duration channel尚未超时),就会执行default分支。这导致default分支在每次循环迭代中都会执行,直到duration channel超时(600秒后),这解释了为什么您看到它多次执行。

问题在于您的for循环没有退出条件,除了duration超时外。在default分支中,您向ch发送了true,但main函数中的if <- ch会立即接收这个值,导致ab函数继续循环并再次进入select,重复执行default分支。

此外,break语句在select中只退出select,而不是退出for循环。因此,即使duration超时,break也不会终止循环,程序会继续运行。

以下是代码的修正版本,添加了循环退出逻辑,并解释了行为:

package main

import (
	"fmt"
	"time"
)

func ab(ch chan bool, i int) {
	duration := time.After(time.Second * time.Duration(600))
	for {
		select {
		case <-duration:
			fmt.Printf(" received value: %d ", i)
			return // 使用return退出函数,而不是break
		default:
			fmt.Printf(" no value received %d ", i)
			ch <- true
			// 注意:这里没有退出循环,所以会继续执行
		}
	}
}

func main() {
	for i := 1; i < 10; i++ {
		fmt.Printf("Testcase %d start", i)
		ch := make(chan bool, 1)
		go ab(ch, i)
		if <-ch {
			fmt.Printf("Testcase %d end", i)
		}
		fmt.Printf("sleeping for 10 seconds")
		time.Sleep(10 * time.Second)
	}
}

在原始代码中,default分支执行的原因:

  • duration channel在600秒后才会发送值,因此在超时前,select总是会进入default分支。
  • 每次循环,default分支向ch发送truemain函数接收后打印信息,但ab函数继续循环,导致default重复执行。
  • 由于ch是缓冲channel(容量1),第一次发送不会阻塞,但后续发送会阻塞,因为main只在每次迭代接收一次。这可能导致死锁,但您的代码中main有睡眠,所以可能不会立即显现。

如果您想限制default分支的执行次数,可以添加计数器或使用其他退出条件。例如,修改ab函数为:

func ab(ch chan bool, i int) {
	duration := time.After(time.Second * time.Duration(600))
	count := 0
	for {
		select {
		case <-duration:
			fmt.Printf(" received value: %d ", i)
			return
		default:
			if count >= 3 {
				return // 限制default执行3次后退出
			}
			fmt.Printf(" no value received %d ", i)
			ch <- true
			count++
		}
	}
}

这确保了default分支只执行三次,然后函数返回。

回到顶部