Go中跨包调用含Mutex的Write函数时锁失效问题排查
Go语言锁失效原因分析与修复
锁失效的核心原因
你遇到的问题本质是每个Goroutine使用的都是独立的DiskConfiguration实例——每次调用Write前都会通过InitDiskConfiguration创建新实例,每个实例内部的sync.Mutex都是独立的锁。锁的作用范围仅限于当前实例,不同实例的锁之间没有互斥关系,所以多个Goroutine可以同时进入各自实例的Write方法,导致同一文件被并发处理。
额外的隐藏问题
你的代码还有一个致命漏洞:当dc.Exists(fileName)返回true时,函数直接return,但此时没有调用dc.M.Unlock(),会导致当前实例的锁永远无法释放,后续调用该实例的Write方法会永久阻塞,引发死锁。
修复方案
方案1:共享同一个DiskConfiguration实例
确保所有Goroutine复用同一个DiskConfiguration对象,让锁成为全局共享的互斥体:
// 全局实例,所有Goroutine共用 var globalDC = &DiskConfiguration{} // 或者用单例模式保证只初始化一次 var once sync.Once var staticDC *DiskConfiguration func InitDiskConfiguration() *DiskConfiguration { once.Do(func() { staticDC = &DiskConfiguration{} }) return staticDC }
方案2:使用全局锁(无状态场景)
如果DiskConfiguration不需要维护其他状态,可以直接用全局锁替代实例锁,同时用defer确保锁一定会释放:
var writeLock sync.Mutex func (dc *DiskConfiguration) Write(fileName string, b []byte) error { writeLock.Lock() defer writeLock.Unlock() if dc.Exists(fileName) { return errors.New(RecordAlreadyExists) } gr := bytes.Fields(debug.Stack())[1] fmt.Println("CORE WRITE FUNC ", fileName, " ", string(gr)) return os.WriteFile(fileName, b, DefaultFilePermission) }
方案3:细粒度文件锁(优化并发)
如果需要更高的并发性能,不想让所有文件写入都串行,可以针对每个文件名单独加锁:
var fileLocks = sync.Map{} func (dc *DiskConfiguration) Write(fileName string, b []byte) error { // 获取或创建对应文件的专属锁 lock, _ := fileLocks.LoadOrStore(fileName, &sync.Mutex{}) mutex := lock.(*sync.Mutex) mutex.Lock() defer mutex.Unlock() if dc.Exists(fileName) { return errors.New(RecordAlreadyExists) } gr := bytes.Fields(debug.Stack())[1] fmt.Println("CORE WRITE FUNC ", fileName, " ", string(gr)) return os.WriteFile(fileName, b, DefaultFilePermission) }
内容的提问来源于stack exchange,提问作者Suhail Gupta
相关产品推荐
相关产品推荐

