首页 > 解决方案 > 在 Go 中修改切片时如何避免竞争条件?

问题描述

func sample(testList []struct{}, testMap map[int64]struct{}) {
    for i, test := range testList {
        // some if conditions to get the matched key
        testList[i] = testMap[key]
    }
}

map 和 slice 的值是相同的类型。我将使用一些匹配的地图值来替换切片中的值。

标签: dictionarygoslice

解决方案


在 Go 中,保护对切片的并发访问有两个方面。

首先,切片是一个连续的内存块,包含零个或多个相同类型的元素;每个都通过其索引 ( 0to len(slice)-1) 访问。
在 Go 中,切片的每个元素都被视为一个独立变量,这意味着可以同时访问/修改同一切片的不同元素。重申一下,只要每次这样的访问,就可以从不同的并发运行的 goroutine
写入索引处的元素N和同一切片的元素。MN != M

因此,您只需要将多个 goroutine 执行的并发访问序列化到切片的同一元素。
换句话说,如果您需要N从多个并发执行的 goroutine 中读取和/或修改索引处的元素,则需要通过互斥锁来保护该元素。

现在请注意,在现实生活中,保护单个互斥锁对单个元素的访问的需求很少发生,因此通常只使用单个互斥锁来保护对切片的任何元素的访问。

其次,切片上的某些操作可能会重新分配包含切片元素的内存块,这有点棘手:比如说,当你这样做时

slice = append(slice, a_new_element)

并且slice没有空间容纳a_new_element,会发生两件事:

  1. append将分配一个新的内存块来保存len(slice)+1元素,然后将原始内存块的内存复制到那里。
  2. append将返回一个新的切片描述符,并且slice变量的内容将被=操作员用它覆盖。

所有这些操作都会自然而然地与对该切片的任何涉及索引的访问(如)竞争slice[N]:例如,在 goroutine 执行append将复制切片的内存。

由此可见,对切片的任何可能重新分配切片内存块的操作以及对保存切片描述符的变量的任何更新都必须与正在修改切片元素的所有 goroutine 同步——无论这些 goroutine 是否同步修改单个切片的元素。

TL;博士

如果您要从多个 goroutine 访问切片,请使用单个互斥锁来保护对切片的任何类型的访问。

如果您最终会检测到互斥体将成为瓶颈,那么您可以使用上述逻辑来降低争用。


推荐阅读