Golang 互斥范围遍及 goroutine 中的共享数组
Golang mutex ranging over shared array in goroutines
假设我有以下代码:
a := []int{1,2,3}
i := 0
var mu = &sync.Mutex{}
for i < 10 {
go func(a *[]int) {
for _, i := range a {
mu.Lock()
fmt.Println(a[0])
mu.Unlock()
}
}(&a)
i++
}
数组是共享资源,正在循环读取。如何保护循环 header 中的数组,我是否需要这样做?还需要将数组作为指针传递给goroutine吗?
首先,一些 Go 术语:
[]int{1, 2, 3}
是一个 切片 ,而不是一个 数组 。数组将写为 [...]int{1, 2, 3}
.
切片是 (start, length, capacity)
的三元组并指向底层数组(通常是 heap-allocated,但这是语言完全向您隐藏的实现细节!)
Go 的内存模型允许任意数量的读取器或(但不是和)至多一个写入器到内存中的任何给定区域。 Go memory model(不幸的是)并没有特别指出同时访问同一个切片中的多个索引的情况,但这样做似乎很好(即它们被视为内存中的不同位置,就像预期)。
所以如果你只是从中读取,根本没有必要保护它。
如果你正在读 并且 写入它,但是 goroutines 不在彼此相同的地方读写(例如,如果 goroutine i
只读取和写入位置 i
) 然后你也不需要同步。此外,您可以同步整个切片(这意味着更少的互斥锁,但 多 更高的争用)或者您可以同步切片中的各个位置(这意味着更低的争用但更多的互斥锁以及获取和释放的锁)。
但是由于 Go 允许函数在范围内捕获变量(也就是说,它们是闭包),所以根本没有理由将数组作为指针传递:
因此,您的代码最惯用的写法是:
a := []int{1,2,3}
for i := 0; i < 10; i++
for i < 10 {
go func() {
for _, i := range a {
fmt.Println(a[0])
}
}()
}
我不太确定上面的代码应该用于什么——因为它将在各种 goroutine 中打印出 a[0]
10 次,这使得它看起来甚至没有使用 slice一种有意义的方式。
首先你应该知道 a := []int{1,2,3}
不是一个数组,它是一个 slice.
切片文字就像没有长度的数组文字。
This is an array literal:
[3]bool{true, true, false}
And this creates the same array as above, then builds a slice that
references it:
[]bool{true, true, false}
[]为空的类型,比如[]int,其实是切片,不是数组。在 Go 中,数组的大小是类型的一部分,因此要真正拥有一个数组,您需要有类似 [16]int 的东西,指向它的指针将是 *[16]int.
问:是否需要将数组作为指针传递给goroutine?
答:号从https://golang.org/doc/effective_go.html#slices
If a function takes a slice argument, changes it makes to the elements
of the slice will be visible to the caller, analogous to passing a
pointer to the underlying array.
假设我有以下代码:
a := []int{1,2,3}
i := 0
var mu = &sync.Mutex{}
for i < 10 {
go func(a *[]int) {
for _, i := range a {
mu.Lock()
fmt.Println(a[0])
mu.Unlock()
}
}(&a)
i++
}
数组是共享资源,正在循环读取。如何保护循环 header 中的数组,我是否需要这样做?还需要将数组作为指针传递给goroutine吗?
首先,一些 Go 术语:
[]int{1, 2, 3}
是一个 切片 ,而不是一个 数组 。数组将写为 [...]int{1, 2, 3}
.
切片是 (start, length, capacity)
的三元组并指向底层数组(通常是 heap-allocated,但这是语言完全向您隐藏的实现细节!)
Go 的内存模型允许任意数量的读取器或(但不是和)至多一个写入器到内存中的任何给定区域。 Go memory model(不幸的是)并没有特别指出同时访问同一个切片中的多个索引的情况,但这样做似乎很好(即它们被视为内存中的不同位置,就像预期)。
所以如果你只是从中读取,根本没有必要保护它。
如果你正在读 并且 写入它,但是 goroutines 不在彼此相同的地方读写(例如,如果 goroutine i
只读取和写入位置 i
) 然后你也不需要同步。此外,您可以同步整个切片(这意味着更少的互斥锁,但 多 更高的争用)或者您可以同步切片中的各个位置(这意味着更低的争用但更多的互斥锁以及获取和释放的锁)。
但是由于 Go 允许函数在范围内捕获变量(也就是说,它们是闭包),所以根本没有理由将数组作为指针传递:
因此,您的代码最惯用的写法是:
a := []int{1,2,3}
for i := 0; i < 10; i++
for i < 10 {
go func() {
for _, i := range a {
fmt.Println(a[0])
}
}()
}
我不太确定上面的代码应该用于什么——因为它将在各种 goroutine 中打印出 a[0]
10 次,这使得它看起来甚至没有使用 slice一种有意义的方式。
首先你应该知道 a := []int{1,2,3}
不是一个数组,它是一个 slice.
切片文字就像没有长度的数组文字。
This is an array literal:
[3]bool{true, true, false}
And this creates the same array as above, then builds a slice that references it:
[]bool{true, true, false}
[]为空的类型,比如[]int,其实是切片,不是数组。在 Go 中,数组的大小是类型的一部分,因此要真正拥有一个数组,您需要有类似 [16]int 的东西,指向它的指针将是 *[16]int.
问:是否需要将数组作为指针传递给goroutine?
答:号从https://golang.org/doc/effective_go.html#slices
If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array.