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

Go协程触发死锁的根因排查与修复方案咨询

死锁根因分析

你的代码触发死锁的核心问题是单个WaitGroup被错误地跨流水线多阶段复用,导致通道关闭逻辑与goroutine退出逻辑形成循环等待:

  1. 负责关闭senderToService的匿名协程调用wg.Wait()时,会等待所有已添加到wg的goroutine(4个sender + 1个service + 1个receiver)完成,但service goroutine需要senderToService关闭才会退出,形成第一个循环:senderToService关闭等待wg计数归0 → wg计数归0等待service退出 → service退出等待senderToService关闭。
  2. 同理,负责关闭serviceToReceiver的匿名协程也需要等待wg计数归0,但receiver goroutine需要serviceToReceiver关闭才会退出,形成第二个循环等待。
  3. 主goroutine的wg.Wait()同样会一直等待所有goroutine完成,最终所有goroutine都陷入阻塞,触发死锁。
修复方案

拆分WaitGroup,让每个流水线阶段使用独立的WaitGroup管理,确保通道关闭逻辑只依赖当前阶段的完成:

package main

import (
    "fmt"
    "sync"
)

func sender(id int, data []int, out chan<- [2]int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < len(data); i += 2 {
        out <- [2]int{data[i], data[i+1]}
    }
}

func service(in <-chan [2]int, c, d int, out chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for pair := range in {
        sum := pair[0] + pair[1]
        if sum >= c && sum <= d {
            out <- sum
        }
    }
}

func receiver(in <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    line := 1
    for sum := range in {
        fmt.Printf("Line %d: Received sum %d\n", line, sum)
        line++
    }
}

func main() {
    data1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    data2 := []int{11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
    data3 := []int{21, 22, 23, 24, 25, 26, 27, 28, 29, 30}
    data4 := []int{31, 32, 33, 34, 35, 36, 37, 38, 39, 40}

    c, d := 15, 50

    senderToService := make(chan [2]int, 10)
    serviceToReceiver := make(chan int, 10)

    // 拆分三个阶段的WaitGroup,各自管理对应阶段的goroutine
    var senderWG sync.WaitGroup
    var serviceWG sync.WaitGroup
    var receiverWG sync.WaitGroup

    // 启动sender阶段goroutine
    senderWG.Add(4)
    go sender(1, data1, senderToService, &senderWG)
    go sender(2, data2, senderToService, &senderWG)
    go sender(3, data3, senderToService, &senderWG)
    go sender(4, data4, senderToService, &senderWG)

    // 仅等待sender阶段完成后,关闭上游通道
    go func() {
        senderWG.Wait()
        close(senderToService)
    }()

    // 启动service阶段goroutine
    serviceWG.Add(1)
    go service(senderToService, c, d, serviceToReceiver, &serviceWG)

    // 仅等待service阶段完成后,关闭中间通道
    go func() {
        serviceWG.Wait()
        close(serviceToReceiver)
    }()

    // 启动receiver阶段goroutine
    receiverWG.Add(1)
    go receiver(serviceToReceiver, &receiverWG)

    // 等待最后一个阶段完成,结束程序
    receiverWG.Wait()
}
补充说明
  • 流水线模式中,每个阶段的生命周期应独立:上游阶段完成后关闭输出通道,下游阶段通过监听通道关闭判断上游完成,进而退出自身。
  • 单个WaitGroup适合管理同一阶段的多个goroutine,跨阶段复用会导致同步逻辑混乱,引发循环等待。

内容的提问来源于stack exchange,提问作者Mirama Khytyn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 00:02:32