在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 的值是相同的类型。我将使用一些匹配的地图值来替换切片中的值。
回答
在 Go 中,保护对切片的并发访问有两个方面。
首先,切片是一个连续的内存块,它包含零个或多个相同类型的元素;每个都通过其索引 ( 0to len(slice)-1)访问。
在 Go 中,切片的每个元素都被视为一个独立变量,这意味着可以同时访问/修改同一切片的不同元素。
重申一下,只要每次这样的访问,都可以从不同的并发运行的 goroutine写入索引处的元素N和M同一切片的元素N != M。
因此,您只需要将多个 goroutine 执行的并发访问序列化到切片的同一元素。
换句话说,如果您需要N从多个并发执行的 goroutine 中读取和/或修改索引处的元素,则需要通过互斥锁保护该元素。
现在请注意,在现实生活中,需要保护单个互斥体对单个元素的访问的情况很少发生,因此通常只使用单个互斥体来保护对切片中任何元素的访问。
其次,切片上的一些操作可能会重新分配包含切片元素的内存块,这里有点棘手:比如说,当你这样做时
slice = append(slice, a_new_element)
并且slice没有空间容纳a_new_element,会发生两件事:
append将分配一个新的内存块来保存len(slice)+1元素,然后将原始内存块的内存复制到那里。append将返回一个新的切片描述符,slice变量的内容将被=运算符覆盖。
所有这些操作都会自然的种族与该切片的任何访问涉及索引(像slice[N]):例如,在一个goroutine中执行特定的索引更新片的元素的尝试可能发生在同一时间够程执行append将被复制切片的内存。
由此可知,任何可能重新分配切片内存块的切片操作以及保存切片描述符的变量的任何更新都必须与所有正在修改切片元素的 goroutine 同步——无论这些 goroutine 是否是与修改单个切片的元素同步。
TL; 博士
使用单个互斥锁来保护任何如果您要从多个 goroutine 访问切片,请对切片的类型的访问。
如果您最终会检测到互斥锁已成为瓶颈,您可以使用上述逻辑降低争用。