cap*_*aig 18 concurrency go 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
Run Code Online (Sandbox Code Playgroud)
它似乎工作,但我没有彻底测试,我不确定它是否安全.一般来说,让多个goroutine写入任何内容都不会让我感觉良好,但在这种情况下,每个goroutine都限制在切片中自己的索引,这是预先分配的.
我想替代方案是通过渠道收集结果,但由于结果顺序很重要,这似乎相当简单.以这种方式写入切片元素是否安全?
icz*_*cza 23
规则很简单:如果多个goroutine 同时访问变量,并且至少有一个访问是写入,则需要同步.
您的示例不违反此规则.您不会写切片值(切片标头),只能读取它(隐式地,当您对其进行索引时).
您没有读取切片元素,只修改切片元素.而且每次仅够程改变单一的,不同的,指定片元素.由于每个切片元素都有自己的地址(自己的内存空间),因此它们就像是不同的变量.这是覆盖在规格:变量:
必须记住的是,如果results没有同步,您无法从切片中读取结果.您在示例中使用的等待组是足够的同步.一旦wg.Wait()返回,您就可以读取切片,因为这只能在所有工作器goroutine调用之后发生wg.Done(),并且在调用之后没有任何工作器goroutines修改元素wg.Done().
例如,这是检查/处理结果的有效(安全)方法:
wg.Wait()
// Safe to read results after the above synchronization point:
fmt.Println(results)
Run Code Online (Sandbox Code Playgroud)
但是如果你想尝试访问results之前的元素wg.Wait(),那就是数据竞争:
// This is data race! Goroutines might still run and modify elements of results!
fmt.Println(results)
wg.Wait()
Run Code Online (Sandbox Code Playgroud)
是的,这是完全合法的:切片有一个数组作为其底层数据存储,并且作为复合类型,数组是一系列“元素”,它们表现为具有不同内存位置的单个变量;同时修改它们很好。
在读取切片的更新内容之前,请务必将您的工作程序 goroutine的关闭与主程序同步。
sync.WaitGroup为此使用- 就像您一样 - 非常好。
此外,正如@icza 所说,您不得修改切片值本身(这是一个包含指向后备存储数组、容量和长度的指针的结构)。