web-dev-qa-db-ja.com

Golangの同時実行性:異なるゴルーチンから同じスライスに追加する方法

(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チャネルを使用する必要があると思います。誰かが例を挙げて貢献できますか?

25
Daniele B

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)
}

すべてのゴルーチンが別のメモリに書き込むので、これは際どいものではありません。

26
Volker

@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)
}
14
chris

チャネルはこれに取り組む最もよい方法です。 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()の方が簡単で明確であると考えています。

0
jimt