Go中如何遍历含Mutex的结构体切片避免锁拷贝
解决Go中遍历包含sync.RWMutex的结构体切片时的拷贝锁问题
报错的核心原因是:range遍历[]Client切片时,循环变量existingClient是切片中元素的副本,而Client结构体包含sync.RWMutex——这是一个不可拷贝的类型(内部维护了锁的状态),拷贝锁会导致锁状态混乱,Go的vet工具会直接抛出错误阻止并发问题。
下面提供两种可行的解决方式:
方式一:将切片改为存储Client指针
把ActiveClients中的clients字段从[]Client改为[]*Client,这样遍历拿到的是原Client的指针,不会发生结构体(包括锁)的拷贝:
首先修改结构体定义:
type ActiveClients struct { clients []*Client // 改为指针切片 mu sync.RWMutex }
然后调整add方法(推荐直接接收指针参数,避免额外拷贝):
func (ac *ActiveClients) add(client *Client) { ac.mu.Lock() defer ac.mu.Unlock() if ac.clients == nil { ac.clients = make([]*Client, 0) } ac.clients = append(ac.clients, client) }
最后修改addToResource方法,同时注意要加ActiveClients的读锁保护遍历过程:
func (ac *ActiveClients) addToResource(clientId int) { ac.mu.RLock() defer ac.mu.RUnlock() for _, existingClient := range ac.clients { if existingClient.id == clientId { existingClient.mu.Lock() defer existingClient.mu.Unlock() existingClient.resource.increment() break // 找到目标后直接退出循环,提升效率 } } }
方式二:通过索引访问原元素(不修改切片类型)
如果不想改动切片的存储类型,可以通过索引遍历切片,直接获取原元素的指针,避免拷贝:
func (ac *ActiveClients) addToResource(clientId int) { ac.mu.RLock() defer ac.mu.RUnlock() // 用索引遍历,直接取切片中元素的地址 for i := range ac.clients { existingClient := &ac.clients[i] if existingClient.id == clientId { existingClient.mu.Lock() defer existingClient.mu.Unlock() existingClient.resource.increment() break } } }
额外注意
不管用哪种方式,遍历ActiveClients.clients时都要加ac.mu的读锁,防止其他goroutine在遍历过程中修改切片(比如调用add方法),引发数据竞争或遍历异常。
内容的提问来源于stack exchange,提问作者Jonathan Voss
相关产品推荐
相关产品推荐

