我有一个包含要完成的工作的切片,以及一个包含所有完成后的结果的切片。以下是我的一般流程的草图:
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仅限于片中自己的索引(已预先分配)。
我认为替代方法是通过渠道收集结果,但是由于结果的顺序很重要,所以这似乎很简单。这样写切片元素是否安全?
规则很简单:如果多个goroutines 同时访问一个变量,并且至少其中之一是写操作,则需要同步。
您的示例不违反此规则。您无需写入切片 值 (slice标头),而仅读取它(隐式地在索引时)。
您不阅读slice 元素 ,只修改了slice元素。并且每个goroutine仅修改单个, 不同的 , 指定的 slice元素。而且,由于每个slice元素都有其自己的地址(自己的内存空间),因此它们就像不同的变量。规格:变量中对此进行了介绍:
__array ,slice和struct类型的 结构化 变量具有可以单独处理的元素和字段。 每个这样的元素就像一个变量。
必须记住的是,如果不results进行同步,则无法从切片中读取结果。您在示例中使用的等待组是足够的同步。一旦wg.Wait()返回,就可以读取切片,因为只有在所有工作程序goroutine都调用之后才能发生该操作wg.Done(),并且所有工作程序goroutine 都不能在调用之后修改元素wg.Done()。
results
wg.Wait()
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()