You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go并发FanIn模式同时收发引发死锁的原因探究

Go FanIn模式死锁问题解析

问题场景

你实现的FanIn模式代码运行后输出部分值就触发死锁,修改接收逻辑后程序恢复正常,下面是具体分析:

错误代码

package main

import (
    "fmt"
)

func main() {
    even := make(chan int)
    odd := make(chan int)
    quit := make(chan int)
    fanin := make(chan int)

    go send(even, odd, quit)

    go receive(even, odd, quit, fanin)

    for v := range fanin {
        fmt.Println(v)
    }

    fmt.Println("about to exit")
}

// send channel
func send(even, odd, quit chan<- int) {
    for i := 0; i < 100; i++ {
        if i%2 == 0 {
            even <- i
        } else {
            odd <- i
        }
    }
    close(quit)
}

// receive channel
func receive(even, odd, quit <-chan int, fanin chan<- int) {
thisLoop:
    for {
        select {
        case fanin <- <-even: // 问题出在这里
        case fanin <- <-odd:
        case <-quit:
            break thisLoop
        }
    }
    close(fanin)
}

死锁报错

...
95
96
99
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /home/floosde/go/src/floosde/main.go:17 +0x194

goroutine 19 [chan receive]:
main.receive(...)
        /home/floosde/go/src/floosde/main.go:41
created by main.main in goroutine 1
        /home/floosde/go/src/floosde/main.go:15 +0x137
exit status 2

修复后的代码

// receive channel
func receive(even, odd, quit <-chan int, fanin chan<- int) {
    thisLoop:
    for {
        select {
        case v := <-even: // 修复后的写法
            fanin <- v 
        case v := <-odd: // 修复后的写法
            fanin <- v
        case <-quit:
            break thisLoop
        }
    }
    close(fanin)
}

核心原因解析

问题的关键在于Go语言select语句的执行逻辑,以及两种写法的表达式求值顺序差异:

  1. 错误写法的致命问题
    对于case fanin <- <-even:,Go会先执行<-even(从even通道接收值),完成这个求值后,才会判断fanin <- 接收的值这个发送操作是否可执行。

    • 当send函数发送完所有100个值并关闭quit后,even/odd通道可能已无剩余值,此时quit通道处于关闭状态(<-quit会立即返回)。
    • 如果select循环选中了fanin <- <-even:这个case,而even通道已经没有值,那么<-even会直接阻塞,程序无法再处理<-quit的case——因为求值<-even的过程已经卡住,根本没到select选择分支的阶段。
    • 最终导致:receive goroutine阻塞在<-even的求值上,main goroutine阻塞在range fanin(因为fanin未被关闭),所有goroutine进入休眠,触发死锁。
  2. 正确写法的逻辑合理性
    修复后的case v := <-even:是标准的接收分支,select会将其作为独立的可执行分支评估:

    • 每次进入select,Go会同时检查所有分支的可执行性:如果even有值,就接收值并发送到fanin;如果even没值,就跳过这个分支,去检查其他分支(比如已关闭的quit通道)。
    • 当quit通道关闭后,<-quit是立即可执行的,select会选中这个分支,触发循环退出,随后关闭fanin,main goroutine的range循环正常结束,程序无死锁。

简单来说,错误写法把“从even取值”和“向fanin发值”绑成了不可拆分的操作,导致取值阻塞时无法响应quit信号;而正确写法将两个操作拆分,让select能灵活处理所有分支,包括quit信号。

内容的提问来源于stack exchange,提问作者Блабла Блаблов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:48:18