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

为何我的Go互斥锁代码中的goroutine发生死锁?

问题:每个Key独立锁的Map出现死锁

问题场景

尝试实现一个为每个Key配备独立锁的Map:创建特定Key的锁时用全局互斥锁保护Map的写入操作,锁创建完成后使用该Key对应的锁执行任务,任务结束后释放锁。但测试单个Key的并发修改时出现死锁。

原代码

package main

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

var count int
var globalMutex *sync.RWMutex
var mutexes map[int]*sync.Mutex

func MyLock(index int) {
    fmt.Println("Aquiring Lock")
    globalMutex.Lock()
    defer globalMutex.Unlock()

    count++

    if mutexes == nil {
        mutexes = make(map[int]*sync.Mutex)
    }

    if _, ok := mutexes[index]; !ok {
        mutexes[index] = &sync.Mutex{}
    }

    fmt.Println("Aquiring 2nd Lock")
    mutexes[index].Lock()
    fmt.Println("Aquired Lock")
}

func MyUnlock(index int) {
    globalMutex.Lock()
    defer globalMutex.Unlock()
    mutexes[index].Unlock()
}

func main() {
    var wg sync.WaitGroup
    globalMutex = &sync.RWMutex{}
    wg.Add(500)
    for i := 0; i < 500; i++ {
        go func(i int) {
            defer wg.Done()

            MyLock(2)

            time.Sleep(1 * time.Second)
            fmt.Println(i)

            MyUnlock(2)
        }(i)
    }

    wg.Wait()
    fmt.Println(mutexes)
    fmt.Println(count)
}

死锁原因

  1. 锁持有逻辑错误:MyLock中先获取globalMutex写锁,随后获取Key对应的锁,但globalMutex的解锁被延迟到MyLock函数结束后执行。这会导致后续goroutine进入MyLock时:
    • 成功获取globalMutex(第一个goroutine的MyLock执行完毕释放后)
    • 尝试获取Key锁时被阻塞,同时持续持有globalMutex
  2. 双向等待形成死锁:第一个goroutine执行完任务后调用MyUnlock,需要获取globalMutex才能解锁Key锁,但此时globalMutex被阻塞在Key锁等待的goroutine持有,最终双方互相等待:
    • 第一个goroutine:持有Key锁 → 等待globalMutex
    • 阻塞的goroutine:持有globalMutex → 等待Key锁

修复方案

  1. 提前释放全局锁:globalMutex仅需保护mutexesMap的读写操作,确认Key锁存在并获取后,立即释放globalMutex,无需持有至函数结束
  2. 解锁无需全局锁:Key锁一旦创建后不会被删除(当前逻辑无删除操作),因此MyUnlock无需获取globalMutex,直接解锁Key锁即可(若后续有删除锁的逻辑,可加全局读锁确保读取安全)

修复后的代码

package main

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

var count int
var globalMutex *sync.RWMutex
var mutexes map[int]*sync.Mutex

func MyLock(index int) {
    fmt.Println("Aquiring global lock")
    globalMutex.Lock()

    count++

    if mutexes == nil {
        mutexes = make(map[int]*sync.Mutex)
    }

    // 确保当前Key的锁存在
    if _, ok := mutexes[index]; !ok {
        mutexes[index] = &sync.Mutex{}
    }
    // 先拿到Key锁的引用,再释放全局锁
    mutex := mutexes[index]
    globalMutex.Unlock()

    fmt.Println("Aquiring key lock")
    mutex.Lock()
    fmt.Println("Aquired key lock")
}

func MyUnlock(index int) {
    // 加全局读锁确保读取锁引用时的安全
    globalMutex.RLock()
    mutex := mutexes[index]
    globalMutex.RUnlock()
    
    mutex.Unlock()
    fmt.Println("Released key lock")
}

func main() {
    var wg sync.WaitGroup
    globalMutex = &sync.RWMutex{}
    mutexes = make(map[int]*sync.Mutex) // 提前初始化简化逻辑
    wg.Add(500)
    for i := 0; i < 500; i++ {
        go func(i int) {
            defer wg.Done()

            MyLock(2)

            time.Sleep(1 * time.Millisecond) // 缩短sleep加快测试
            fmt.Println(i)

            MyUnlock(2)
        }(i)
    }

    wg.Wait()
    fmt.Println(mutexes)
    fmt.Println(count)
}

额外优化说明

  • 提前初始化mutexesMap,避免MyLock中的nil判断逻辑
  • MyUnlock使用全局读锁,确保读取Key锁引用时不会被其他写操作干扰
  • 缩短Sleep时间,加快测试验证效率

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 06:17:02