GoLang的并发如何避免死锁

Go 中,并发死锁是一个常见问题,尤其是在使用 goroutinechannelmutex 时。避免死锁需要理解死锁的根源,然后采取相应策略。下面我给你系统讲解:


1️⃣ 死锁的常见原因

  1. 循环等待

    多个 goroutine 相互等待对方持有的资源,例如:

    Go 复制代码
    var mu1, mu2 sync.Mutex
    
    go func() {
        mu1.Lock()
        time.Sleep(time.Second)
        mu2.Lock()  // 等待 mu2
        mu2.Unlock()
        mu1.Unlock()
    }()
    
    go func() {
        mu2.Lock()
        time.Sleep(time.Second)
        mu1.Lock()  // 等待 mu1
        mu1.Unlock()
        mu2.Unlock()
    }()

    → 互相等待,形成死锁。

  2. 未关闭的 channel

    • 发送者发送到没有接收者的 channel。

    • 接收者等待发送者,但发送者没有启动。

      例:

    Go 复制代码
    ch := make(chan int)
    ch <- 1  // 如果没人接收,死锁
  3. 缓冲区满的 channel

    • 无缓冲 channel 或缓冲已满的 channel 都可能阻塞发送者。

    • 如果没有其他 goroutine 及时接收,可能死锁。

  4. 所有 goroutine 阻塞

    • 主 goroutine 等待子 goroutine 完成,但子 goroutine 因等待主 goroutine 或其他资源而阻塞。

    • Go 运行时会直接报 fatal error: all goroutines are asleep - deadlock!


2️⃣ 避免死锁的策略

(A) 正确使用 channel

  1. 用缓冲 channel 避免阻塞

    复制代码
    ch := make(chan int, 1) // 带缓冲
    ch <- 1  // 不会立即阻塞
  2. 确保接收者存在

    复制代码
    go func() {
        val := <-ch
        fmt.Println(val)
    }()
    ch <- 1
  3. 关闭 channel

    • 对于只读操作,发送方可以关闭 channel,让接收方通过 for range 循环退出:

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

  4. 使用 select 避免阻塞

    复制代码
    select {
    case ch <- 1:
        fmt.Println("sent")
    default:
        fmt.Println("channel full, skip")
    }

(B) 正确使用 mutex(互斥锁)

  1. 避免嵌套锁

    • 如果必须锁多个 mutex,确保所有 goroutine 按相同顺序加锁

      mu1.Lock()
      mu2.Lock()
      // ...
      mu2.Unlock()
      mu1.Unlock()

  2. 使用 defer 解锁

    复制代码
    mu.Lock()
    defer mu.Unlock()
    // 函数结束自动释放锁

(C) 使用 WaitGroup 或信号量

  • sync.WaitGroup 可以避免主 goroutine 提前退出导致死锁。

  • 例:

    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
    defer wg.Done()
    fmt.Println("goroutine 1")
    }()

    go func() {
    defer wg.Done()
    fmt.Println("goroutine 2")
    }()

    wg.Wait() // 等待所有 goroutine 完成


(D) 检查所有 goroutine 的阻塞点

  • 发送/接收 channel

  • mutex 锁

  • select default 分支是否被阻塞

  • WaitGroup Done() 是否漏掉


(E) 工具辅助

  • 使用 go run -race 检测竞态条件。

  • 使用 pprofruntime.Stack() 分析死锁。

  • Go 1.18+ 有时会自动检测全局死锁并报错。


✅ 小结

避免 Go 死锁核心原则:

  1. 保证 所有 channel 的发送和接收成对存在

  2. 加锁顺序统一,避免循环等待。

  3. 合理使用缓冲 channelselect

  4. 用 WaitGroup 或 context 控制 goroutine 生命周期

  5. 工具检测 + defer 解锁,减少人为错误。


相关推荐
Aaswk11 小时前
Java Lambda 表达式与流处理
java·开发语言·python
万邦科技Lafite12 小时前
京东item_get接口实战案例:实时商品价格监控全流程解析
java·开发语言·数据库·python·开放api·淘宝开放平台
东方小月12 小时前
5分钟搞懂Harness Engineering(驾驭工程):从提示词到AI Agent的进化之路
前端·后端·架构
Cyber4K13 小时前
【Python专项】进阶语法-系统资源监控与数据采集(1)
开发语言·python·php
Le_ee13 小时前
ctfweb:php/php短标签/.haccess+图片马/XXE
开发语言·前端·php
yong999014 小时前
MATLAB读取高光谱图像
开发语言·matlab
2zcode14 小时前
基于MATLAB的肝病风险评估与分期分析系统设计与实现
开发语言·matlab
小小de风呀14 小时前
de风——【从零开始学C++】(五):内存管理
开发语言·c++
ooseabiscuit14 小时前
Laravel6.x核心优化与特性全解析
android·开发语言·javascript
折哥的程序人生 · 物流技术专研14 小时前
Java面试85题图解版(一):基础核心篇
java·开发语言·后端·面试