web-dev-qa-db-ja.com

異なるスライス要素を同時に書き込むことはできますか

実行する作業を含むスライスと、すべてが完了したときの結果を含むスライスがあります。以下は私の一般的なプロセスのスケッチです:

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

それは動作するようですが、私はそれを完全にテストしていませんし、それが安全かどうかはわかりません。一般に、複数のゴルーチンにanythingへの書き込みを許可するのは気分が悪くなりますが、この場合、各ゴルーチンは、事前に割り当てられたスライス内の独自のインデックスに制限されます。

代替手段はチャネル経由で結果を収集することだと思いますが、結果の順序が重要であるため、これはかなり単純に見えました。この方法でスライス要素に書き込むことは安全ですか?

18
captncraig

ルールは単純です。複数のゴルーチンが 変数 に同時にアクセスし、アクセスの少なくとも1つが書き込みの場合、同期が必要です。

あなたの例はこのルールに違反していません。スライスvalue(スライスヘッダー)を書き込まず、読み取るだけです(暗黙的に、インデックスを付けるとき)。

スライス要素を読み取るのではなく、スライス要素を変更するだけです。そして、各goroutineは、単一のdifferentdesignatedスライス要素のみを変更します。また、各スライス要素には独自のアドレス(独自のメモリ空間)があるため、それらは別個の変数のようです。これは Spec:Variables: でカバーされています

arrayslice 、および struct タイプの構造化変数には、次のような要素とフィールドがあります アドレス指定 個別。 このような各要素は変数のように機能します。

心に留めておく必要があるのは、同期せずにresultsスライスから結果を読み取ることはできないということです。また、例で使用したウェイトグループは十分な同期です。 wg.Wait()が戻るとスライスを読み取ることができます。これは、すべてのワーカーゴルーチンがwg.Done()を呼び出した後でのみ発生し、wg.Done()

たとえば、これは結果を確認/処理する有効な(safe)方法です。

_wg.Wait()
// Safe to read results after the above synchronization point:
fmt.Println(results)
_

ただし、wg.Wait()の前にresultsの要素にアクセスしようとすると、データ競合になります。

_// This is data race! Goroutines might still run and modify elements of results!
fmt.Println(results)
wg.Wait()
_
23
icza

はい、それは完全に合法です:スライスは基本となるデータストレージとして配列を持ちます。また、複合型である配列は、異なるメモリ位置を持つ個別の変数として動作する「要素」のシーケンスです。それらを同時に変更することは問題ありません。

スライスの更新された内容を読み取る前に、ワーカーゴルーチンのシャットダウンをメインのゴルーチンと同期するようにしてください。

このためにsync.WaitGroupを使用するのは、あなたがそうであるように、まったく問題ありません。

また、@ iczaが言ったように、スライス値自体(これは、バッキングストレージアレイへのポインター、容量、および長さを含む構造体)を変更してはなりません。

4
kostix