Golang中的互斥锁在多个goroutine中遍历共享数组。

huangapple go评论79阅读模式
英文:

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++
}

数组a是一个共享资源,并且在循环中被读取。我应该如何在循环头部保护数组?是否需要将数组作为指针传递给goroutine?

英文:

Say I have the following code:

a := []int{1,2,3}
i := 0
var mu = &amp;sync.Mutex{}
for i &lt; 10 {
    go func(a *[]int) {
        for _, i := range a {
            mu.Lock()
            fmt.Println(a[0])
            mu.Unlock()
        }
    }(&amp;a)
    i++
}

The array is a shared resource and is being read from in the loop. How do I protect the array in the loop header and do I need to? Also is it necessary to pass the array to the goroutine as a pointer?

答案1

得分: 3

首先,一些Go语言的术语:

[]int{1, 2, 3} 是一个切片(slice),而不是数组(array)。数组的写法应该是[...]int{1, 2, 3}

切片是一个三元组(start, length, capacity),它指向一个底层数组(通常是在堆上分配的,但这是语言完全隐藏的实现细节!)

Go的内存模型允许任意数量的读取者或(但不是并且)最多一个写入者访问内存中的任何给定区域。Go的内存模型(不幸的是)没有明确指出同时访问同一个切片的多个索引的情况,但似乎这样做是可以的(也就是说,它们被视为内存中的不同位置,这是可以预期的)。

因此,如果你只是从切片中读取数据,就不需要进行任何保护。

如果你既要读取又要写入切片,但是各个goroutine之间不会读取和写入相同的位置(例如,如果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],看起来似乎没有以有意义的方式使用切片。

英文:

First, some Go terminology:

[]int{1, 2, 3} is a slice, not an array. An array would be written as [...]int{1, 2, 3}.

A slice is a triplet of (start, length, capacity) and points to an underlying array (usually heap-allocated, but this is an implementation detail that the language completely hides from you!)

Go's memory model allows any number of readers or (but not and) at most one writer to any given region in memory. The Go memory model (unfortunately) doesn't specifically call out the case of accessing multiple indices into the same slice concurrently, but it appears to be fine to do so (i.e. they are treated as distinct locations in memory, as would be expected).

So if you're just reading from it, it is not necessary to protect it at all.

If you're reading and writing to it, but the goroutines don't read and write to the same places as each other (for example, if goroutine i only reads and writes to position i) then you also don't need synchronization. Moreover, you could either synchronize the entire slice (which means fewer mutexes, but much higher contention) or you could synchronize the individual positions in the slice (which means much lower contention but many more mutexes and locks acquired and released).

But since Go allows functions to capture variables in scope (that is, they are closures) there's really no reason to pass the array as a pointer at all:

Your code would thus be most idiomatically be written as:

a := []int{1,2,3}
for i := 0; i &lt; 10; i++ 
for i &lt; 10 {
    go func() {
        for _, i := range a {
            fmt.Println(a[0])
        }
    }()
}

I'm not really sure what the above code is supposed to be for- since it's going to print out a[0] 10 times in various goroutines, which makes it look like it's not even using the slice in a meaningful way.

答案2

得分: 2

首先,你应该知道a := []int{1,2,3}不是一个数组,而是一个切片

切片字面量类似于没有长度的数组字面量。

这是一个数组字面量:

[3]bool{true, true, false}

而这个语句创建了与上述数组相同的数组,然后构建了一个引用它的切片:

[]bool{true, true, false}

带有空的[]的类型,比如[]int,实际上是切片,而不是数组。在Go语言中,数组的大小是类型的一部分,所以要真正拥有一个数组,你需要像[16]int这样的声明,而指向它的指针将是*[16]int。

问题:将数组作为指针传递给goroutine是必要的吗?

回答:不是必要的。根据https://golang.org/doc/effective_go.html#slices的说明:

如果一个函数接受一个切片参数,并对切片的元素进行更改,这些更改将对调用者可见,类似于将指向底层数组的指针传递给函数。

英文:

First you shuold know a := []int{1,2,3} is not an array, it is a slice.

A slice literal is like an array literal without the length.

> 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}

Types with empty [], such as []int are actually slices, not arrays. In Go, the size of an array is part of the type, so to actually have an array you would need to have something like [16]int, and the pointer to that would be *[16]int.

Q: is it necessary to pass the array to the goroutine as a pointer?

A: No. From 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.

huangapple
  • 本文由 发表于 2017年2月15日 07:56:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/42238425.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定