多设备并发采集时,为每台设备维护一把锁是很自然的做法:
map[GUID]*sync.Mutex
它避免设备 A 的慢操作阻塞设备 B,却引入了第二层并发:存放锁的 map 由谁保护?
两个 goroutine 同时执行“没有锁—创建锁—写入 map”,可能产生并发写,也可能让同一设备得到两把不同的锁。安全实现至少要保证“查找或创建”是原子的:
type DeviceLocks struct {
mu sync.Mutex
locks map[string]*sync.Mutex
}
func (d *DeviceLocks) Get(guid string) *sync.Mutex {
d.mu.Lock()
defer d.mu.Unlock()
if lock, ok := d.locks[guid]; ok {
return lock
}
lock := &sync.Mutex{}
d.locks[guid] = lock
return lock
}
这段实现还有两个没解决的问题。
第一,锁对象何时删除?设备下线后直接从 map 删除并不安全,其他 goroutine 可能仍持有旧指针。设备规模有限时,让锁常驻反而是更简单的选择。
第二,我们真正想保护的是什么?如果一次业务操作同时修改连接、缓存和设备状态,只锁某个 map 并不能保证业务一致性。
go test -race 能发现数据竞争,却不会告诉我们锁的粒度是否正确。并发安全最终保护的不是某个变量,而是一条业务不变量。