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

读取元素数量未知的两个Go通道时出现死锁问题排查

问题

尝试生成数字发送至通道,由另一个函数读取处理后,根据是否出错返回out和errorCh两个通道。由于错误数量未知,通过defer关闭out和errorCh以标识无更多元素。

示例中,将数字发送至in通道,再将该通道传入run函数获取out和errorCh,最后从这两个通道读取结果。通过defer关闭in通道,预期run函数收到in通道关闭信号后处理完所有元素,进而关闭out和errorCh,使select语句感知到通道关闭,但实际出现了死锁。请问是否存在通道未正确关闭的情况?死锁原因是什么?

原代码:

package main

import (
    "errors"
    "fmt"
)

func run(in <-chan int) (chan int, chan error) {
    out := make(chan int)
    errorCh := make(chan error)

    go func() {
        defer close(out)
        defer close(errorCh)

        for i := range in {
            if i%2 == 0 {
                out <- i
            } else {
                errorCh <- errors.New("we don't like odd numbers")
            }
        }
    }()
    return out, errorCh
}

func main() {
    in := make(chan int)
    out := make(chan int)
    errors := make(chan error)

    // 生成数字发送至in通道
    go func(in chan int) {
        defer close(in)
        for i := 0; i < 10; i++ {
            fmt.Println("  input", i)
            in <- i
        }
    }(in)

    // run函数读取in通道并处理,返回out和errorCh通道
    go func(in chan int) {
        out, errors = run(in)
    }(in)

    // 持续读取out和errorCh通道,直至in通道所有数字处理完成
    for {
        select {
        case i, ok := <-out:
            if !ok { // out通道已关闭
                return // 处理完成
            }
            fmt.Println("done", i)
        case err, ok := <-errors:
            if !ok {
                return
            }
            if err != nil {
                fmt.Println(err)
            }
        }
    }
}
死锁原因分析
  • 通道引用错误:main函数中先初始化了两个空的无缓冲通道out和errors,随后在一个独立goroutine中调用run(in)重新给这两个变量赋值。但main的select语句从一开始就监听的是最初创建的空通道,完全没有使用run函数返回的、实际会发送数据并关闭的通道。这导致run内部的goroutine往返回通道发送数据时没有接收方,而main的select一直阻塞在无数据、也不会被关闭的初始通道上,最终所有goroutine都陷入阻塞,触发死锁。
  • 同步缺失:赋值out和errors的goroutine与main的select之间没有同步机制,main极有可能在goroutine完成赋值前就开始监听无效通道,彻底偏离预期逻辑。
修复方案
  1. 移除main中初始创建的无效通道,直接在main goroutine中调用run获取正确的通道引用
  2. 修改退出逻辑:不能仅因一个通道关闭就直接返回,需等待两个通道都关闭后再退出,避免遗漏未处理的数据

修复后的代码:

package main

import (
    "errors"
    "fmt"
)

func run(in <-chan int) (chan int, chan error) {
    out := make(chan int)
    errorCh := make(chan error)

    go func() {
        defer close(out)
        defer close(errorCh)

        for i := range in {
            if i%2 == 0 {
                out <- i
            } else {
                errorCh <- errors.New("we don't like odd numbers")
            }
        }
    }()
    return out, errorCh
}

func main() {
    in := make(chan int)

    // 生成数字发送至in通道
    go func(in chan int) {
        defer close(in)
        for i := 0; i < 10; i++ {
            fmt.Println("  input", i)
            in <- i
        }
    }(in)

    // 直接获取run返回的有效通道
    out, errorCh := run(in)

    outOpen := true
    errOpen := true
    // 等待两个通道都关闭后再退出
    for outOpen || errOpen {
        select {
        case i, ok := <-out:
            if !ok {
                outOpen = false
                continue
            }
            fmt.Println("done", i)
        case err, ok := <-errorCh:
            if !ok {
                errOpen = false
                continue
            }
            if err != nil {
                fmt.Println(err)
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Jonathan Kittell

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 05:45:01