Golang中SelectStmt控制流问题探讨

Golang中SelectStmt控制流问题探讨 我一直使用 cfg 包来生成 Go 程序的 CFG。在处理 SelectStmt 时,我发现生成的 CFG 有点奇怪。例如:

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
}

对于这段代码,生成的 CFG 如下:

.0: # 入口 c1, c2 chan int c1 ← 1 c1 ← 3 <-c2 后继块: 2 3

.1: # select.done 返回 true

.2: # select.body fmt.Print(“c1 ← 1”) 后继块: 1

.3: # select.next 后继块: 4 5

.4: # select.body fmt.Print(“c1 ← 3”) 后继块: 1

.5: # select.next 后继块: 6 7

.6: # select.body 后继块: 1

.7: # select.next fmt.Print(“default”) 后继块: 1

.8: # 不可达的返回

在块 0 中,我们可以看到所有的通信操作都按顺序出现,这表明 CommClauses 是按顺序求值的,这与 switch 语句类似。我原本期望的是类似下面的结构。应该从块 0 出发有多条路径(而不是真/假路径):

.0: # 入口 msg []string c1, c2 chan int 后继块: 1, 2, 3, 4

.1 # select.body c1 ← 1 fmt.Print(“c1 ← 1”) 后继块 : 5

.2 # select.body c3 ← 1 fmt.Print(“c1 ← 3”) 后继块 : 5

.3 # select.body ← c2 后继块 : 5

.4 # select.body fmt.Print(“default”) 后继块 : 5

.5 # select.done 返回 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不同,它并不是按顺序求值,而是会同时评估所有case的通信操作就绪状态。你观察到的CFG生成结果反映了select在底层实现中的实际行为。

select语句在编译时会被转换为特定的运行时调用,所有case会被打包成一个数组传递给selectgo函数。这个函数会随机选择一个就绪的case执行,如果没有case就绪且存在default,则执行default。

你提供的CFG显示块0包含了所有通信操作,这实际上对应的是select语句的初始化阶段,这些操作会被评估以确定哪些channel已就绪。随后的块结构反映了selectgo函数可能的分支路径。

以下是更符合Go运行时行为的示例代码结构:

func Foo() bool {
    var c1, c2 chan int
    // select语句在编译时会被转换为类似以下结构
    cases := []runtime.SelectCase{
        {
            Dir:  runtime.SelectSend,
            Chan: c1,
            Send: 1,
        },
        {
            Dir:  runtime.SelectSend,
            Chan: c1,
            Send: 3,
        },
        {
            Dir:  runtime.SelectRecv,
            Chan: c2,
        },
        {
            Dir: runtime.SelectDefault,
        },
    }
    
    chosen, _ := runtime.Select(cases)
    
    switch chosen {
    case 0:
        fmt.Print("c1 <- 1")
    case 1:
        fmt.Print("c1 <- 3")
    case 2:
        // <-c2
    case 3:
        fmt.Print("default")
    }
    
    return true
}

你期望的CFG结构更接近逻辑视图,而实际生成的CFG反映了编译器的底层实现。cfg包生成的CFG显示了select语句在SSA(静态单赋值)形式下的实际控制流,其中所有通信操作在入口块中被初始化,然后通过运行时函数决定执行路径。

这种差异是因为select不是简单的条件分支,而是涉及并发原语的复杂操作。每个case的通信操作都需要在运行时动态评估,而不是编译时的静态顺序求值。

回到顶部