提问人:Jonathan Voss 提问时间:7/9/2023 更新时间:7/10/2023 访问量:241
如何在 Go 中对包含互斥锁的结构切片进行范围调整
How to range over a slice of structs that contain Mutexes in Go
问:
我正在试验 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}”。客户端包含同步。RWMutex”。
如何在不复制锁的情况下对切片进行范围调整?
答:
在写问题时找到了解决方案。解决方案是使用指针切片而不是结构切片 -- use 而不是:clients []*Client
clients []Client
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)
}
func (ac *ActiveClients) incrementResource(clientId int) {
for _, existingClient := range ac.clients {
if existingClient.id == clientId {
existingClient.mu.Lock()
defer existingClient.mu.Unlock()
existingClient.resource.increment()
}
}
}
完整的工作示例在这里。对于有经验的 Go 开发人员来说,这可能是显而易见的,但我找不到这个特定案例的答案,所以希望这篇文章能帮助其他学习者。(解决方案与代码中错误的位置是分开的,因此如果没有足够的内存模型经验,乍一看并不明显。
评论
该语句将 的元素分配给局部变量 。该值将被复制。没有引用语义for _, v := range s
s
v
该命令会警告您已复制互斥锁字段。互斥锁一旦使用就不应复制。go vet
这并不是 中唯一的问题。该函数修改局部变量中客户端的副本,而不是切片中的客户端。由于局部变量在从函数返回时被丢弃,因此该函数不起作用。运行程序 https://go.dev/play/p/kL9GZSL6d2j 以查看问题的演示。incrementResource
incrementResource
通过指针访问 slice 元素来修复 bug。incrementResource
func (ac *ActiveClients) addToResource(clientId int) {
for i := range ac.clients {
existingClient := &ac.clients[i] // existingClient is ptr to slice element
if existingClient.id == clientId {
existingClient.Lock()
defer existingClient.Unlock()
existingClient.resource.increment()
fmt.Println("data in addToResource: ", existingClient.resource.data)
}
}
}
这是修复程序: https://go.dev/play/p/wMSUOjoTauB
上述更改解决了问题中的问题,但这并不是应用程序的唯一问题。对 in 方法的调用在增长切片时复制值。此副本会在切片元素上创建数据争用,并违反使用后不应复制互斥锁的规则。append
ActiveClients.add
Client
要修复所有问题,请使用 的切片代替 。当我们使用它时,请利用 对切片的处理。*Client
Client
append
nil
type ActiveClients struct {
clients []*Client
mu sync.RWMutex
}
func (ac *ActiveClients) add(client *Client) {
ac.mu.Lock()
defer ac.mu.Unlock()
ac.clients = append(ac.clients, client)
}
这是最终程序: https://go.dev/play/p/miNK90ZDNCu
评论
map[string]*Client
[]*Client
评论