我可以同时写入不同的切片元素

时间:2018-04-17 13:23:03

标签: go concurrency slice goroutine

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

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都限制在切片中自己的索引,这是预先分配的。

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

3 个答案:

答案 0 :(得分:21)

规则很简单:如果多个goroutine同时访问variable,并且至少有一个访问是写入,则需要同步。

您的示例不违反此规则。你没有写切片(切片头),你只读它(隐含地,当你索引它时)。

您没有读取切片元素,只修改切片元素。每个goroutine只修改一个不同的指定的切片元素。由于每个切片元素都有自己的地址(自己的内存空间),因此它们就像是不同的变量。这包含在Spec: Variables:

  

arrayslicestruct类型的结构化变量的元素和字段可能分别为addressed每个此类元素都像变量一样。

必须牢记的是,如果没有同步,您无法从results切片读取结果。您在示例中使用的等待组是足够的同步。一旦wg.Wait()返回,您就可以读取切片,因为只有在所有工作器goroutine调用wg.Done()之后才能读取切片,并且在调用wg.Done()之后,没有任何工作器goroutines修改元素。 / p>

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

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

但是如果你试图在results之前访问wg.Wait()的元素,那就是数据竞争:

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

答案 1 :(得分:4)

是的,它是完全合法的:一个切片有一个数组作为它的底层数据存储,并且,作为一个复合类型,一个数组是一系列“元素”,它们表现为具有不同内存位置的单个变量;同时修改它们很好。

请确保将您的worker goroutines的关闭同步 在读取切片的更新内容之前的主要内容。

使用sync.WaitGroup就可以了 - 完全没问题。

另外,正如@icza所说,你不能修改切片值本身(这是一个包含指向后备存储阵列的指针的结构,容量和长度)。

答案 2 :(得分:0)

是的,你可以。

tldr

Example (Parallel)示例中,它在lambda handler中具有相同的示例代码

Google := func(ctx context.Context, query string) ([]Result, error) {
    g, ctx := errgroup.WithContext(ctx)

    searches := []Search{Web, Image, Video}
    results := make([]Result, len(searches))
    for i, search := range searches {
        i, search := i, search

        g.Go(func() error {
            result, err := search(ctx, query)
            if err == nil {
                results[i] = result
            }
            return err
        })
    }
    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}
// ...