一尘不染

我可以同时写不同的slice元素吗

go

我有一个包含要完成的工作的切片,以及一个包含所有完成后的结果的切片。以下是我的一般流程的草图:

var results = make([]Result, len(jobs))
wg := sync.WaitGroup{}
for i, job := range jobs {
    wg.Add(1)
    go func(i int, j job) {
        defer wg.Done()
        var r Result = doWork(j)
        results[i] = r
    }(i, job)
}
wg.Wait()
// Use results

它似乎可行,但我尚未对其进行全面测试,因此不确定这样做是否安全。通常,让多个goroutine写入 任何内容
不会让我感觉很好,但是在这种情况下,每个goroutine仅限于片中自己的索引(已预先分配)。

我认为替代方法是通过渠道收集结果,但是由于结果的顺序很重要,所以这似乎很简单。这样写切片元素是否安全?


阅读 252

收藏
2020-07-02

共1个答案

一尘不染

规则很简单:如果多个goroutines
同时访问一个变量,并且至少其中之一是写操作,则需要同步。

您的示例不违反此规则。您无需写入切片 (slice标头),而仅读取它(隐式地在索引时)。

您不阅读slice 元素 ,只修改了slice元素。并且每个goroutine仅修改单个, 不同的指定的
slice元素。而且,由于每个slice元素都有其自己的地址(自己的内存空间),因此它们就像不同的变量。规格:变量中对此进行了介绍

__array
slicestruct类型的
结构化 变量具有可以单独处理的元素和字段。
每个这样的元素就像一个变量。

必须记住的是,如果不results进行同步,则无法从切片中读取结果。您在示例中使用的等待组是足够的同步。一旦wg.Wait()返回,就可以读取切片,因为只有在所有工作程序goroutine都调用之后才能发生该操作wg.Done(),并且所有工作程序goroutine
都不能在调用之后修改元素wg.Done()

例如,这是检查/处理结果的有效( 安全 )方法:

wg.Wait()
// Safe to read results after the above synchronization point:
fmt.Println(results)

但是,如果您尝试访问resultsbefore 的元素wg.Wait(),那就是一场数据竞赛:

// This is data race! Goroutines might still run and modify elements of results!
fmt.Println(results)
wg.Wait()
2020-07-02