英文:
How to avoid race conditions when modifying slices in Go?
问题
func sample(testList []struct{}, testMap map[int64]struct{}) {
for i, test := range testList {
// 一些条件判断来获取匹配的键
testList[i] = testMap[key]
}
}
map和slice的值是相同类型的。
我将使用一些匹配的map值来替换slice中的值。
英文:
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]
}
}
The value of map and slice are the same type.
I will use some matched map values to replace the values in the slice.
答案1
得分: 8
在Go语言中,保护并发访问切片有两个方面。
首先,切片是一个连续的内存块,其中包含零个或多个相同类型的元素;每个元素通过其索引(0
到len(slice)-1
)进行访问。
在Go语言中,切片的每个元素都被视为独立的变量,这意味着可以同时访问/修改同一个切片的不同元素。
换句话说,只要对同一个切片的索引N
和M
处的元素进行访问时满足N != M
,就可以在不同的并发运行的goroutine中同时对其进行写入。
因此,你只需要对多个goroutine执行的并发访问同一个切片的元素进行串行化。
换句话说,如果你需要从多个并发执行的goroutine中读取和/或修改索引N
处的元素,你需要使用互斥锁来保护该元素。
现在请注意,在实际应用中,需要使用单独的互斥锁来保护对各个元素的访问的情况相对较少,所以通常只需使用一个互斥锁来保护对切片的任何元素的访问。
其次,对切片的某些操作可能会重新分配包含切片元素的内存块,这时情况会有些棘手:例如,当你执行以下操作时:
slice = append(slice, a_new_element)
并且slice
没有足够的空间来容纳a_new_element
,会发生两件事情:
append
将分配一个新的内存块来容纳len(slice)+1
个元素,然后将原始内存块的内容_复制_到新的内存块中。append
将返回一个新的切片描述符,并且slice
变量的内容将被=
运算符覆盖。
所有这些操作自然会与涉及索引的切片访问(如slice[N]
)发生_竞争_:例如,一个goroutine尝试更新切片在特定索引处的元素可能会与执行append
的goroutine同时进行切片内存的复制。
由此可见,任何可能重新分配切片内存块的切片操作以及更新持有切片描述符的变量的任何操作都必须与修改切片元素的所有goroutine同步,无论这些goroutine是否在修改各个切片元素时进行了同步。
简而言之
如果你要从多个goroutine访问切片,请使用单个互斥锁来保护对切片的_任何_类型的访问。
如果最终发现互斥锁成为了瓶颈,你可以使用上述逻辑来降低争用。
英文:
In Go, there are two aspects to protecting concurrent access to slices.
First, a slice is a contiguous memory block which contains zero or more elements of the same type; each is accessed by its index (0
to len(slice)-1
).
In Go, each element of a slice is treated as an independent variable, which means it's OK to concurrently access/modify different elements of the same slice.
To reiterate, it's OK to write to the elements at indexes N
and M
of the same slice from different concurrently running goroutines as long as N != M
for every such access.
Hence, you only need to serialize concurrent accesses performed by multiple goroutines to the same element of a slice.
In other words, if you need to read and/or modify an element at index N
from multiple concurrently executing goroutines you need to protect that element by a mutex.
Now note that in real life the need to protect accesses to individual elements by individual mutexes happens reasonably seldom, so usually one just uses a single mutex to protect access to any element of a slice.
Second, some operations on a slice may reallocate the memory block which contains the slice's elements, and here it gets a bit tricky: say, when you do
slice = append(slice, a_new_element)
and slice
has no room to hold a_new_element
, two things will happen:
append
will allocate a new memory block to holdlen(slice)+1
elements and then copy the memory of the original memory block there.append
will return a new slice descriptor and the contents of theslice
variable will be overwritten with it by the=
operator.
All these operations will naturally race with any access to that slice which involves indexing (like slice[N]
): for instance, an attempt to update a slice's element at a particular index performed by a goroutine might happen at the same time the goroutine performing append
will be copying the slice's memory.
From this follows, that any operation on a slice which might reallocate the slice's memory block and any update of the variable(s) holding the slice's descriptor must be synchronized with all the goroutines which are modifying the slice's elements—no matter whether those goroutines are synchronized with regard to modifying individual slice's elements.
TL;DR
Use a single mutex to protect any kind of access to a slice if you're about to access it from more than a single goroutine.
If you will eventually detect that mutex will have become a bottleneck, you have ways to lower the contention using the logic described above.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论