【问题标题】:Golang mutex ranging over shared array in goroutinesGolang互斥锁在goroutines中的共享数组上
【发布时间】:2017-02-14 23:56:12
【问题描述】:

假设我有以下代码:

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

数组是共享资源,正在循环中读取。如何保护循环头中的数组,我需要吗?还有必要将数组作为指针传递给goroutine吗?

【问题讨论】:

    标签: arrays go concurrency locking mutex


    【解决方案1】:

    首先,一些 Go 术语:

    []int{1, 2, 3} 是一个切片,而不是一个数组。数组将写为[...]int{1, 2, 3}

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

    Go 的内存模型允许任意数量的读取器或(但不是和)最多一个写入器访问内存中的任何给定区域。 Go memory model(不幸的是)没有明确指出同时访问多个索引到同一个切片中的情况,但这样做似乎很好(即它们被视为内存中的不同位置,正如预期的那样) .

    因此,如果您只是从中读取,则根本没有必要保护它。

    如果你正在读取写入它,但 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] 10 次,这使得它看起来甚至没有以有意义的方式使用切片。

    【讨论】:

    • 很棒的解释。谢谢!该代码只是一个示例,因此我可以理解指针、数组和锁是如何协同工作的。我似乎经常遇到这种特殊的模式。无论如何,再次感谢。
    • 需要注意的是,有一个常见错误(go vet 检测到;因此我建议运行)是循环变量(如range 中的i)具有以下范围持续时间长于给定的迭代;特别是:for i := range a { go func() { fmt.Printf("%d", i) }() } 可能会打印出 3、3、3 或各种其他值,因为所有 goroutine 都捕获相同的 i。解决方案是做for i := range a { go func(i int) { fmt.Printf("%d", i) }(i) }
    • 你是什么意思他们持续更长时间?它们仍然只在循环范围内,对吧?
    • 上面的例子展示了我试图解释的内容(尽管它可能更清楚)。具体来说,循环体中的闭包将捕获循环变量(上面的i)。因此,对于i,所有闭包都将相同引用相同的值,而不是每个都有自己的值。因此,有必要将其作为参数传递或以其他方式为其赋予每次迭代的本地范围,而不是整个循环。
    【解决方案2】:

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

    切片文字就像没有长度的数组文字。

    这是一个数组字面量:

    [3]bool{true, true, false}
    

    这会创建与上面相同的数组,然后构建一个切片 引用它:

    []bool{true, true, false}
    

    [] 为空的类型,例如 []int,实际上是切片,而不是数组。在 Go 中,数组的大小是类型的一部分,所以要真正拥有一个数组,你需要有类似 [16]int 的东西,指向它的指针是 *[16]int。

    问:是否需要将数组作为指针传递给goroutine?

    答:没有。来自https://golang.org/doc/effective_go.html#slices

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

    【讨论】:

      猜你喜欢
      • 2014-12-18
      • 1970-01-01
      • 2021-10-10
      • 2012-06-29
      • 1970-01-01
      • 1970-01-01
      • 2011-12-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多