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

自定义Go Mutex遭遇死锁求助:all goroutines are asleep - deadlock

问题描述

尝试实现一个带自定义行为的简单Mutex,但测试时触发死锁错误:all goroutines are asleep - deadlock!。第一个for循环代码块运行正常,但第二个代码块调用m.Unlock()时失败。实现逻辑是用带缓冲的channel,通过发送值实现锁,不清楚为什么会出现goroutine死锁问题。

错误日志

Result:
=== RUN   TestNiceMutex_Lock
=== RUN   TestNiceMutex_Lock/test
   nicemutex_test.go:36: main: lock
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
testing.(*T).Run(0xc00007b380, {0xcee048?, 0xc1f6cd?}, 0xcf8388)
C:/Program Files/Go/src/testing/testing.go:1649 +0x3c8
testing.runTests.func1(0xdecd00?)
C:/Program Files/Go/src/testing/testing.go:2054 +0x3e
testing.tRunner(0xc00007b380, 0xc0000b7c48)
C:/Program Files/Go/src/testing/testing.go:1595 +0xff
testing.runTests(0xc00009a140?, {0xde0d90, 0x1, 0x1}, {0xbc3345?, 0xc0000b7d08?, 0x0?})
C:/Program Files/Go/src/testing/testing.go:2052 +0x445
testing.(*M).Run(0xc00009a140)
C:/Program Files/Go/src/testing/testing.go:1925 +0x636
main.main()
_testmain.go:47 +0x19c

goroutine 6 [chan receive]:
testing.(*T).Run(0xc00007b520, {0xceae57?, 0xc78420?}, 0xc00023c000)
C:/Program Files/Go/src/testing/testing.go:1649 +0x3c8
simply/nicemutex.TestNiceMutex_Lock(0x0?)
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:26 +0xba
testing.tRunner(0xc00007b520, 0xcf8388)
C:/Program Files/Go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
C:/Program Files/Go/src/testing/testing.go:1648 +0x3ad

(content is repeated)

goroutine 97 [chan send]:
simply/nicemutex.lock()
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex.go:6
simply/nicemutex.TestNiceMutex_Lock.func1()
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:12 +0x25
created by simply/nicemutex.TestNiceMutex_Lock in goroutine 6
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:11 +0x2c


goroutine 117 [chan send]:
simply/nicemutex.(*NiceMutex).Lock(0xc000250008)
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex.go:25 +0x99
simply/nicemutex.TestNiceMutex_Lock.func2.1()
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:32 +0x5f
created by simply/nicemutex.TestNiceMutex_Lock.func2 in goroutine 116
C:/Users/Aleksey/go/src/simply/nicemutex/nicemutex_test.go:30 +0xa5


Process finished with the exit code 1

测试代码

package nicemutex

import (
    "sync"
    "testing"
    "time"
)

func TestNiceMutex_Lock(t *testing.T) {
    for i := 0; i < 100; i++ {
        go func() {
            lock()
            unlock()
        }()
        lock()
        unlock()
    }

    tests := []struct {
        name string
    }{
        {"test"},
    }
    wg := sync.WaitGroup{}
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            var m NiceMutex
            m.Lock()
            wg.Add(1)
            go func() {
                defer m.Unlock()
                m.Lock()
                t.Log("go: lock")
                wg.Done()
            }()
            t.Log("main: lock")
            time.Sleep(time.Second * 1)
            m.Unlock() // <- 错误发生处
            wg.Wait()
        })
    }
}

package nicemutex

var internalLock = make(chan struct{}, 1)

func lock() {
    internalLock <- struct{}{} // <- 错误发生处
}

func unlock() {
    <-internalLock
}

type NiceMutex struct {
    ch chan struct{}
}

func (m *NiceMutex) Lock() {
    defer unlock()
    lock()

    if m.ch == nil {
        m.ch = make(chan struct{}, 1)
    }
    m.ch <- struct{}{}
}

func (m *NiceMutex) Unlock() {
    defer unlock()
    lock() // <- 错误发生处

    if m.ch == nil {
        return
    }
    <-m.ch
}

更新后的代码(仍存在相同问题)

package nicemutex

import (
    "log"
    "time"
)

var (
    internalLock2 = make(chan struct{}, 1)
    TraceEnabled2 = false
)

type NiceMutex2 struct {
    ch       chan struct{}
    lockedOn int64
}

func (m *NiceMutex2) Lock() {
    internalLock2 <- struct{}{}
    defer func() { <-internalLock2 }()

    m.lockedOn = time.Now().UnixNano()

    if m.ch == nil {
        m.ch = make(chan struct{}, 1)
    }
    m.ch <- struct{}{}

    trace2("locked")
}

func (m *NiceMutex2) LockForSec() {
    internalLock2 <- struct{}{}
    defer func() { <-internalLock2 }()

    on := time.Now().UnixNano()
    m.lockedOn = on

    if m.ch == nil {
        m.ch = make(chan struct{}, 1)
    }
    m.ch <- struct{}{}

    go func(lockedOn int64) {
        <-time.After(time.Second)

        internalLock2 <- struct{}{}
        defer func() { <-internalLock2 }()

        if m.lockedOn == lockedOn {
            select {
            case <-m.ch:
                trace2("timeout used", true)
            default:
                trace2("timeout ignore")
            }
        }
    }(on)

    trace2("locked")
}

func (m *NiceMutex2) Unlock() {
    internalLock2 <- struct{}{}
    defer func() { <-internalLock2 }()

    m.lockedOn = time.Now().UnixNano()

    if m.ch == nil {
        return
    }
    select {
    case <-m.ch:
    default:
    }

    trace2("unlocked")
}

func trace2(msg string, ignoreEnabling ...bool) {
    if TraceEnabled2 || (len(ignoreEnabling) > 0 && ignoreEnabling[0]) {
        log.Println(msg)
    }
}
问题分析与解决

核心死锁原因

  1. 全局锁的单点竞争:你用了一个全局的带缓冲channel(internalLock/internalLock2)作为所有Mutex实例的内部锁,当多个goroutine同时调用Lock/Unlock时,会竞争这个全局资源,一旦缓冲被占满,后续发送操作会阻塞。
  2. 实例锁的逻辑冲突:以NiceMutex.Lock为例,主goroutine调用m.Lock()后,m.ch的缓冲已被占满;子goroutine调用m.Lock()时,即使拿到全局锁,也会卡在m.ch <- struct{}{}步骤,且全局锁未释放,导致主goroutine的m.Unlock()无法获取全局锁,最终所有goroutine阻塞触发死锁。
  3. 更新后的代码未解决根本问题:internalLock2依然是全局单点竞争,LockForSec中启动的超时goroutine还会额外抢占全局锁,加剧阻塞风险。

修复方案

  • 移除全局锁:每个Mutex实例应拥有独立的同步原语,用channel实现Mutex的正确方式是让每个实例持有一个缓冲为1的channel,Lock时发送值,Unlock时接收值,无需全局锁。
  • 修正锁逻辑:确保Lock/Unlock仅针对当前实例的channel操作,避免跨实例竞争。

修复后的示例代码:

package nicemutex

import (
    "sync"
    "testing"
    "time"
)

type NiceMutex struct {
    ch chan struct{}
}

// 初始化Mutex,确保channel缓冲为1
func NewNiceMutex() *NiceMutex {
    return &NiceMutex{
        ch: make(chan struct{}, 1),
    }
}

func (m *NiceMutex) Lock() {
    m.ch <- struct{}{}
}

func (m *NiceMutex) Unlock() {
    select {
    case <-m.ch:
    default:
        // 避免重复解锁导致阻塞
    }
}

func TestNiceMutex_Lock(t *testing.T) {
    tests := []struct {
        name string
    }{
        {"test"},
    }
    wg := sync.WaitGroup{}
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            m := NewNiceMutex()
            m.Lock()
            wg.Add(1)
            go func() {
                defer m.Unlock()
                m.Lock()
                t.Log("go: lock")
                wg.Done()
            }()
            t.Log("main: lock")
            time.Sleep(time.Second * 1)
            m.Unlock()
            wg.Wait()
        })
    }
}

额外说明

如果需要实现超时自动释放的锁(如LockForSec),需注意:

  • 每个实例的状态变量要用sync.Mutex保护,避免并发读写冲突
  • 超时释放时要校验锁的当前持有者,防止误释放其他goroutine持有的锁

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 09:07:02