我有一个包含要完成的工作的切片,以及在完成所有操作时将包含结果的切片。以下是我的一般过程的草图:
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都限制在切片中自己的索引,这是预先分配的。
我认为替代方案是通过渠道收集结果,但由于结果顺序很重要,这似乎相当简单。以这种方式写入切片元素是否安全?
答案 0 :(得分:21)
规则很简单:如果多个goroutine同时访问variable,并且至少有一个访问是写入,则需要同步。
您的示例不违反此规则。你没有写切片值(切片头),你只读它(隐含地,当你索引它时)。
您没有读取切片元素,只修改切片元素。每个goroutine只修改一个不同的,指定的切片元素。由于每个切片元素都有自己的地址(自己的内存空间),因此它们就像是不同的变量。这包含在Spec: Variables:
中array,slice和struct类型的结构化变量的元素和字段可能分别为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)
是的,你可以。
在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
}
// ...