如何实现Go并发安全的map/slice以更高效管理独占式资源?
实现方案说明
你提到的用atomic.CompareAndSwapInt64优化的思路是完全可行的,甚至不需要你说的「找到资源再加锁」,靠原子操作就能实现无锁的资源抢占,完全避免全局锁的性能开销,也能天然解决并发抢占同一资源的冲突问题。不过你给出的参考代码有两处明显错误:
CompareAndSwapInt64方法需要3个入参:变量指针、预期旧值、要替换的新值,你漏了旧值0的入参- 代码里有拼写错误:
Resouce应为Resource,同时遍历的应该是接收器m的成员变量,而不是全局的Resources
修正后的完整实现
import ( "errors" "sync/atomic" ) type Resource struct { InUse int64 // 0代表未占用,1代表已占用,所有读写必须用原子操作 Path string // 资源路径,例如/dev/chardeviceXXX } type ResourceManager struct { Resources []*Resource } // GetUnusedResource 抢占空闲资源 func (m *ResourceManager) GetUnusedResource() (*Resource, error) { for _, res := range m.Resources { // 原子操作:只有当前InUse为0时才会修改为1,返回true代表抢占成功 if atomic.CompareAndSwapInt64(&res.InUse, 0, 1) { return res, nil } } return nil, errors.New("all resources are in use") } // ReleaseResource 释放占用的资源 func (m *ResourceManager) ReleaseResource(res *Resource) error { // 可选校验:判断资源是否属于当前管理器,避免传入非法资源 if atomic.LoadInt64(&res.InUse) == 0 { return errors.New("resource is already released") } atomic.StoreInt64(&res.InUse, 0) return nil }
这个实现的优势是完全没有全局锁,多个goroutine可以同时遍历资源列表抢占,CAS原子性保证了同一资源只会被一个goroutine抢占成功,冲突的goroutine会自动跳过继续查找下一个资源,在资源数量较多的场景下,并发性能远高于全局锁方案。
单元测试方案
核心测试场景覆盖如下:
- 基础功能校验:单goroutine下申请、释放资源正常,资源占满后返回预期错误
- 无超发校验:启动远多于资源数的goroutine同时抢占资源,验证同一时间最多和资源总数相等的goroutine能拿到资源
- 并发释放校验:占满资源后随机释放部分资源,验证等待的goroutine可以正常抢到释放的资源
- 异常操作校验:重复释放同一个资源返回预期错误,不会破坏资源状态
- 稳定性校验:长时间跑数百万次申请、释放循环,验证无panic、无死锁、无状态异常
测试代码示例
import ( "sync" "sync/atomic" "testing" ) // 测试资源不会超发 func TestResourceNoOverIssue(t *testing.T) { // 初始化10个测试资源 mgr := &ResourceManager{ Resources: make([]*Resource, 10), } for i := range mgr.Resources { mgr.Resources[i] = &Resource{InUse: 0} } const goroutineNum = 100 var acquireCount int32 var wg sync.WaitGroup wg.Add(goroutineNum) // 启动100个goroutine同时抢资源,且不释放 for i := 0; i < goroutineNum; i++ { go func() { defer wg.Done() _, err := mgr.GetUnusedResource() if err == nil { atomic.AddInt32(&acquireCount, 1) } }() } wg.Wait() if atomic.LoadInt32(&acquireCount) != 10 { t.Fatalf("resource over issue, expected max 10 acquired, got %d", acquireCount) } } // 测试并发申请释放的稳定性 func TestConcurrentAcquireRelease(t *testing.T) { mgr := &ResourceManager{ Resources: make([]*Resource, 10), } for i := range mgr.Resources { mgr.Resources[i] = &Resource{InUse: 0} } const goroutineNum = 100 const loopCount = 10000 var wg sync.WaitGroup wg.Add(goroutineNum) for i := 0; i < goroutineNum; i++ { go func() { defer wg.Done() for j := 0; j < loopCount; j++ { res, err := mgr.GetUnusedResource() if err == nil { // 模拟业务占用资源的耗时 _ = mgr.ReleaseResource(res) } } }() } wg.Wait() // 验证所有资源最终都被释放 for _, res := range mgr.Resources { if atomic.LoadInt64(&res.InUse) != 0 { t.Fatal("resource leak found, not all resources are released") } } }
内容的提问来源于stack exchange,提问作者Liam Kelly
相关产品推荐
相关产品推荐

