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

Can I concurrently write different slice elements

我有一个切片包含要完成的工作,还有一个切片包含完成所有工作后的结果。以下是我的大致流程示意图:

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 写入 anything 感觉很好,但在这种情况下,每个 goroutine 都被限制在切片中自己的索引,这是预先分配的。

我想另一种方法是通过渠道收集结果,但由于结果的顺序很重要,这似乎很简单。这样写入 slice 元素安全吗?

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

你的例子没有违反这条规则。你不写切片(切片header),你只读它(隐含地,当你索引它时)。

您不读取切片元素,您只修改切片元素。并且每个 goroutine 仅修改单个 不同指定 切片元素。由于每个 slice 元素都有自己的地址(自己的内存space),它们就像不同的变量。 Spec: Variables:

中对此进行了介绍

Structured variables of array, slice, and struct types have elements and fields that may be addressed individually. Each such element acts like a variable.

必须牢记的是,您无法在没有同步的情况下从 results 切片中读取结果。您在示例中使用的等待组是足够的同步。您可以读取切片一次 wg.Wait() returns,因为这只会发生在所有调用 wg.Done() 的 worker goroutines 和 none worker goroutines 修改元素之后称为 wg.Done().

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

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

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

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

是的,这是完全合法的:一个切片有一个数组作为它的底层数据存储,并且,作为一种复合类型,数组是一个 "elements" 的序列,它表现为具有不同内存位置的独立变量;同时修改它们是可以的。

只需确保将您的 worker goroutines 的关闭与 读取切片更新内容之前的主要部分。

为此使用 sync.WaitGroup 非常好。

此外,正如@icza 所说,您不得修改切片值本身(这是一个包含指向后备存储数组、容量和长度的指针的结构)。

是的,你可以。

tldr

golang.org/x/sync/errgroup example, it has the same example code in Example (Parallel)

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