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

Go协程死锁排查:用WaitGroup仍休眠死锁且无法持续通信

Go并发通道死锁问题分析

我正在学习Go并发编程,希望两个goroutine通过通道持续传递更新后的值:一个goroutine对数值加2,另一个减1。但运行以下代码后出现死锁错误,协程无法持续交互,即使未调用wg.Done()也无法无限循环,请问代码存在什么问题?

问题代码

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func addTwo(r chan int, e chan int) {
    val := <-r
    fmt.Println("AT Recieved: ", val)
    time.Sleep(1 * time.Second)
    e <- val + 2
}

func subtractOne(r chan int, e chan int) {
    val := <-r
    fmt.Println("SO Recieved: ", val)
    time.Sleep(1 * time.Second)
    e <- val - 1
}

func main() {
    ch := make(chan int)
    ch2 := make(chan int)

    wg.Add(1)
    go addTwo(ch, ch2)

    wg.Add(1)
    go subtractOne(ch2, ch)

    ch <- 1
    wg.Wait()

}

运行输出

AT Recieved:  1
SO Recieved:  3
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x4b2de8?)
    /usr/lib/go-1.18/src/runtime/sema.go:56 +0x25
sync.(*WaitGroup).Wait(0x0?)
    /usr/lib/go-1.18/src/sync/waitgroup.go:136 +0x52
main.main()

问题原因分析

  • goroutine仅执行单次逻辑就终止:addTwo和subtractOne函数只完成一次接收、处理、发送操作就退出,没有循环逻辑支撑持续交互。
  • 通道阻塞触发死锁:
    1. 主goroutine向ch发送初始值1后,addTwo接收并处理为3,发送到ch2;
    2. subtractOne接收3并处理为2,发送到ch;
    3. 此时两个业务goroutine已全部执行完毕退出,ch中的2无goroutine接收,主goroutine卡在wg.Wait(),所有goroutine进入等待状态,触发死锁。
  • WaitGroup未正确使用:两个goroutine都未调用wg.Done(),但这并非死锁直接原因,只是让主goroutine持续等待,最终还是因通道阻塞和goroutine退出触发死锁。

修复方案

要实现持续交互,需给goroutine添加循环逻辑,同时通过控制通道实现优雅终止:

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func addTwo(r chan int, e chan int, stop chan struct{}) {
    defer wg.Done()
    for {
        select {
        case val := <-r:
            fmt.Println("AT Recieved: ", val)
            time.Sleep(1 * time.Second)
            e <- val + 2
        case <-stop:
            fmt.Println("addTwo goroutine exiting")
            return
        }
    }
}

func subtractOne(r chan int, e chan int, stop chan struct{}) {
    defer wg.Done()
    for {
        select {
        case val := <-r:
            fmt.Println("SO Recieved: ", val)
            time.Sleep(1 * time.Second)
            e <- val - 1
        case <-stop:
            fmt.Println("subtractOne goroutine exiting")
            return
        }
    }
}

func main() {
    ch := make(chan int)
    ch2 := make(chan int)
    stop := make(chan struct{})

    wg.Add(2)
    go addTwo(ch, ch2, stop)
    go subtractOne(ch2, ch, stop)

    // 发送初始值启动交互
    ch <- 1
    // 控制运行时长后终止程序
    time.Sleep(5 * time.Second)
    close(stop)

    wg.Wait()
    fmt.Println("Main goroutine exiting")
}

修复说明

  • 添加for循环让goroutine持续监听通道,处理数值传递逻辑;
  • 使用select结合stop通道,实现goroutine的优雅终止,避免无限运行;
  • 每个goroutine添加defer wg.Done(),确保WaitGroup能正确等待所有goroutine退出;
  • 主goroutine通过time.Sleep控制交互时长,关闭stop通道触发goroutine退出。

内容的提问来源于stack exchange,提问作者Om Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:33:27