Golang中SelectStmt控制流问题探讨
Golang中SelectStmt控制流问题探讨 我一直使用cfg包来生成Go程序的控制流图。在处理SelectStmt时,我发现生成的控制流图有点奇怪。例如:
func Foo() bool {
var c1, c2 chan int
select {
case c1 <- 1:
fmt.Print("c1 <- 1")
case c1 <- 3:
fmt.Print("c1 <- 3")
case <-c2:
default:
fmt.Print("default")
}
return true
}
对于这段代码,生成的控制流图如下:
.0: # entry
c1, c2 chan int
c1 ← 1
c1 ← 3
<-c2
succs: 2 3
.1: # select.done
return true
.2: # select.body
fmt.Print(“c1 ← 1”)
succs: 1
.3: # select.next
succs: 4 5
.4: # select.body
fmt.Print(“c1 ← 3”)
succs: 1
.5: # select.next
succs: 6 7
.6: # select.body
succs: 1
.7: # select.next
fmt.Print(“default”)
succs: 1
.8: # unreachable.return
在块0中,我们可以看到所有的通信操作都顺序出现,这表明case子句是按顺序求值的,这与switch语句类似。我原本期望的是类似下面的结构。应该从块0分出多条路径(而不是真/假路径):
.0: # entry
msg []string
c1, c2 chan int
succs: 1, 2, 3, 4
.1 # select.body
c1 ← 1
fmt.Print(“c1 ← 1”)
succs : 5
.2 # select.body
c3 ← 1
fmt.Print(“c1 ← 3”)
succs : 5
.3 # select.body
← c2
succs : 5
.4 # select.body
fmt.Print(“default”)
succs : 5
.5 # select.done
return true
我是不是遗漏了什么?
更多关于Golang中SelectStmt控制流问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang中SelectStmt控制流问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go语言中,select语句的控制流确实与switch语句不同,但你的观察是正确的:select的case子句是按顺序求值的,只是求值方式有特殊规则。
select语句的执行流程如下:
- 所有case的通道表达式和发送值表达式按源码顺序被求值
- 从所有就绪的case中随机选择一个执行
- 如果没有case就绪且存在default,则执行default
- 如果没有case就绪且没有default,则阻塞直到某个case就绪
你生成的控制流图反映了这个顺序求值过程。每个.select.next块对应检查下一个case是否就绪。这是正确的表示方式,因为:
- 通道操作(发送/接收)的求值是顺序的
- 但实际执行哪个case是非确定性的
示例代码说明:
func ExampleSelect() {
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
// 所有表达式按顺序求值:ch1、1、ch2、2
select {
case ch1 <- 1: // 表达式求值:ch1, 1
fmt.Println("sent 1 to ch1")
case ch2 <- 2: // 表达式求值:ch2, 2
fmt.Println("sent 2 to ch2")
default:
fmt.Println("default")
}
}
对于你的代码,控制流图正确显示了:
- 块0:按顺序求值所有case表达式
- 块2-7:依次检查每个case是否就绪
- 块1:select完成后的统一出口
你期望的"多路径分支"结构不适用于select,因为:
- 所有case表达式必须在选择执行路径前完成求值
- 实际执行路径取决于运行时哪些通道操作就绪
所以cfg包生成的控制流图是正确的,它准确反映了Go语言规范中select语句的语义。

