我正在尝试Go,并尝试在服务器中进行并发状态管理的各种事情。假设我们有以下内容:
type Resource struct {
data int
}
func (r *Resource) increment () {
r.data++
}
type Client struct {
id int
resource Resource
mu sync.RWMutex
}
type ActiveClients struct {
clients []Client
mu sync.RWMutex
}
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)
}
字符串ActiveClients.mu
将用于阅读和写入ActiveClients.clients
片,Client.mu
将用于读取和写入Client.resource
片。现在,假设我们想要迭代ActiveClients.clients
来更新其中一个资源。以下内容将创建错误:
func (ac *ActiveClients) addToResource(clientId int) {
for _, existingClient := range ac.clients {
if existingClient.id == clientId {
existingClient.Lock()
defer existingClient.Unlock()
existingClient.resource.increment()
}
}
}
型
这将产生“range var existingClient copies lock:{modulename}。客户端包含sync.RWMutex”。
如何在不复制锁的情况下在切片上进行范围调整?
2条答案
按热度按时间vq8itlhq1#
for _, v := range s
语句将s
的元素赋值给局部变量v
。值被复制。没有引用语义学go vet
命令警告您互斥体字段已被复制。互斥体一旦使用就不应该被复制。这不是
incrementResource
中唯一的问题。函数修改的是局部变量中的client的副本,而不是切片中的client。因为局部变量在从函数返回时被丢弃,所以函数incrementResource
没有效果。运行程序https://go.dev/play/p/kL9GZSL6d2j以查看问题的演示。通过指针访问slice元素修复了
incrementResource
中的bug。字符串
下面是修复的程序:https://go.dev/play/p/wMSUOjoTauB的
上述更改修复了问题中的问题,但这不是应用程序的唯一问题。方法
ActiveClients.add
中对append
的调用在增长切片时复制Client
的值。这种复制会在切片元素上造成数据竞争,并且违反了互斥体一旦使用就不应复制的规则。要修复所有问题,请使用
*Client
切片,而不是Client
。当我们在做这件事的时候,利用append
对nil
片的处理。型
以下是最后的程序:https://go.dev/play/p/miNK90ZDNCu的
v1uwarro2#
在写问题的时候找到了答案。解决方案是使用一片指针而不是一片结构体-使用
clients []*Client
而不是clients []Client
:字符串
完整的工作示例是here。对于有经验的Go开发人员来说,这可能是显而易见的,但我无法找到这个特定案例的答案,所以希望这个条目能帮助其他学习者。(解决方案与代码中错误的位置是分开的,因此如果没有足够的内存模型经验,乍一看并不明显。