(aへのポインター)構造体を同じスライスに追加したい同時goroutineがあります。それをGoでどのように記述して、同時実行を安全にしますか?
これは、待機グループを使用した同時実行性が危険なコードです。
var wg sync.WaitGroup
MySlice = make([]*MyStruct)
for _, param := range params {
wg.Add(1)
go func(param string) {
defer wg.Done()
OneOfMyStructs := getMyStruct(param)
MySlice = append(MySlice, &OneOfMyStructs)
}(param)
}
wg.Wait()
並行性の安全のためにgoチャネルを使用する必要があると思います。誰かが例を挙げて貢献できますか?
Sync.MutexでMySlice = append(MySlice, &OneOfMyStructs)
を保護することに問題はありません。しかし、もちろん、バッファサイズlen(params)
の結果チャネルを持つことができます。すべてのゴルーチンが回答を送信し、作業が完了すると、この結果チャネルから収集します。
params
のサイズが固定されている場合:
MySlice = make([]*MyStruct, len(params))
for i, param := range params {
wg.Add(1)
go func(i int, param string) {
defer wg.Done()
OneOfMyStructs := getMyStruct(param)
MySlice[i] = &OneOfMyStructs
}(i, param)
}
すべてのゴルーチンが別のメモリに書き込むので、これは際どいものではありません。
@jimtによって投稿された回答は、チャネルで送信された最後の値を見逃し、最後のdefer wg.Done()
が呼び出されることはないという点で、正確ではありません。以下のスニペットに修正があります。
https://play.golang.org/p/7N4sxD-Bai
package main
import "fmt"
import "sync"
type T int
func main() {
var slice []T
var wg sync.WaitGroup
queue := make(chan T, 1)
// Create our data and send it into the queue.
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
// defer wg.Done() <- will result in the last int to be missed in the receiving channel
queue <- T(i)
}(i)
}
go func() {
// defer wg.Done() <- Never gets called since the 100 `Done()` calls are made above, resulting in the `Wait()` to continue on before this is executed
for t := range queue {
slice = append(slice, t)
wg.Done() // ** move the `Done()` call here
}
}()
wg.Wait()
// now prints off all 100 int values
fmt.Println(slice)
}
チャネルはこれに取り組む最もよい方法です。 go playground で実行できる例を次に示します。
_package main
import "fmt"
import "sync"
import "runtime"
type T int
func main() {
var slice []T
var wg sync.WaitGroup
queue := make(chan T, 1)
// Create our data and send it into the queue.
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
defer wg.Done()
// Do stuff.
runtime.Gosched()
queue <- T(i)
}(i)
}
// Poll the queue for data and append it to the slice.
// Since this happens synchronously and in the same
// goroutine/thread, this can be considered safe.
go func() {
defer wg.Done()
for t := range queue {
slice = append(slice, t)
}
}()
// Wait for everything to finish.
wg.Wait()
fmt.Println(slice)
}
_
注:runtime.Gosched()
呼び出しが存在するのは、これらのゴルーチンがスケジューラに渡されないためです。このスケジューラをトリガーするために明示的に何かを実行しないと、デッドロックが発生します。別のオプションは、いくつかのI/O(例:stdoutへの出力)を実行することでした。しかし、私はruntime.Gosched()
の方が簡単で明確であると考えています。