如何在Go中包含Mutexes的结构片上进行范围调整

vof42yt1  于 2023-08-01  发布在  Go
关注(0)|答案(2)|浏览(88)

我正在尝试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”。
如何在不复制锁的情况下在切片上进行范围调整?

vq8itlhq

vq8itlhq1#

for _, v := range s语句将s的元素赋值给局部变量v。值被复制。没有引用语义学
go vet命令警告您互斥体字段已被复制。互斥体一旦使用就不应该被复制。
这不是incrementResource中唯一的问题。函数修改的是局部变量中的client的副本,而不是切片中的client。因为局部变量在从函数返回时被丢弃,所以函数incrementResource没有效果。运行程序https://go.dev/play/p/kL9GZSL6d2j以查看问题的演示。
通过指针访问slice元素修复了incrementResource中的bug。

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
上述更改修复了问题中的问题,但这不是应用程序的唯一问题。方法ActiveClients.add中对append的调用在增长切片时复制Client的值。这种复制会在切片元素上造成数据竞争,并且违反了互斥体一旦使用就不应复制的规则。
要修复所有问题,请使用*Client切片,而不是Client。当我们在做这件事的时候,利用appendnil片的处理。

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

v1uwarro

v1uwarro2#

在写问题的时候找到了答案。解决方案是使用一片指针而不是一片结构体-使用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()
        }
    }
}

字符串
完整的工作示例是here。对于有经验的Go开发人员来说,这可能是显而易见的,但我无法找到这个特定案例的答案,所以希望这个条目能帮助其他学习者。(解决方案与代码中错误的位置是分开的,因此如果没有足够的内存模型经验,乍一看并不明显。

相关问题