web-dev-qa-db-ja.com

同時読み取り/書き込み操作に対するGolangマップの安全性は?

Goブログによると、

マップは同時使用に対して安全ではありません。マップを同時に読み書きしたときに何が起こるかは定義されていません。並行して実行されているゴルーチンからマップを読み書きする必要がある場合、アクセスは何らかの同期メカニズムによって仲介される必要があります。 (ソース: https://blog.golang.org/go-maps-in-action

誰もこれについて詳しく説明できますか?同時読み取り操作はルーチン間で許可されているように見えますが、同時読み取り/書き込み操作は、同じキーの読み取りと書き込みを試みると競合状態を生成する場合があります。

場合によっては、この最後のリスクを軽減できますか?例えば:

  • 関数Aはkを生成し、m [k] = 0に設定します。これは、Aがマップmに書き込む唯一の時間です。 kはmにないことが知られています。
  • Aはkを同時に実行する関数Bに渡します
  • 次に、Aはm [k]を読み取ります。 m [k] == 0の場合、m [k]!= 0の場合のみ続行します
  • Bはマップでkを探します。見つかった場合、Bはm [k]を正の整数に設定します。そうでない場合、kがmになるまで待機します。

これは(明らかに)コードではありませんが、AとBの両方がmにアクセスしようとしても、競合状態がないか、または、追加の制約。

32
John D.

Golang 1.6より前は、同時読み取りは問題ありませんが、同時書き込みは問題ありませんが、書き込みと同時読み取りは問題ありません。 Golang 1.6以降、マップは書き込まれているときに読み取ることができません。したがって、Golang 1.6以降では、同時アクセスマップは次のようになります。

package main

import (
    "sync"
    "time"
)

var m = map[string]int{"a": 1}
var lock = sync.RWMutex{}

func main() {
    go Read()
    time.Sleep(1 * time.Second)
    go Write()
    time.Sleep(1 * time.Minute)
}

func Read() {
    for {
        read()
    }
}

func Write() {
    for {
        write()
    }
}

func read() {
    lock.RLock()
    defer lock.RUnlock()
    _ = m["a"]
}

func write() {
    lock.Lock()
    defer lock.Unlock()
    m["b"] = 2
}

または、以下のエラーが表示されます: enter image description here

ADDED:

go run -race race.goを使用してレースを検出できます

read関数を変更します。

func read() {
    // lock.RLock()
    // defer lock.RUnlock()
    _ = m["a"]
}

enter image description here

別の選択肢:

既知のとおり、mapはバケットによって実装され、sync.RWMutexはすべてのバケットをロックします。 concurrent-mapfnv32を使用してキーを分割し、すべてのバケットで1つのsync.RWMutexを使用します。

41
Bryce

同時読み取り(読み取り専用)は問題ありません。同時書き込みおよび/または読み取りは問題ありません。

複数のゴルーチンは、アクセスが同期されている場合にのみ同じマップを書き込みおよび/または読み取ることができます。 sync パッケージ経由、チャンネル付き、または他の手段で。

あなたの例:

  1. 関数Aはkを生成し、m [k] = 0に設定します。これは、Aがマップmに書き込む唯一の時間です。 kはmにないことが知られています。
  2. Aはkを同時に実行する関数Bに渡します
  3. 次に、Aはm [k]を読み取ります。 m [k] == 0の場合、m [k]!= 0の場合のみ続行します
  4. Bはマップでkを探します。見つかった場合、Bはm [k]を正の整数に設定します。そうでない場合、kがmになるまで待機します。

あなたの例には2つのゴルーチンがあります:AとB、Aはmを読み込もうとし(ステップ3)、Bはそれを書き込もうとします(ステップ4)。同期は行われないため(何も言及していません)、これだけでは許可されていません。

どういう意味ですか?未決定とは、Bがmを書き込んでも、Aが変更を監視しないことを意味します。または、Aでさえ発生しなかった変更を観察することがあります。または、パニックが発生する場合があります。または、この非同期同時アクセスにより地球が爆発する可能性があります(ただし、後者の場合の可能性は非常に小さく、1e-40未満である可能性があります)。

関連する質問:

同時アクセスのあるマップ

Goのマップについてスレッドセーフでないとはどういう意味ですか?

Goでマップを使用する際にgoroutine/thread-safetyを無視する危険性は何ですか?

12
icza

Go 1.6リリースノート

ランタイムは、マップの同時誤用の軽量でベストエフォートの検出を追加しました。いつものように、1つのゴルーチンがマップに書き込んでいる場合、他のゴルーチンがマップを同時に読み書きすることはできません。ランタイムがこの状態を検出すると、診断を出力し、プログラムをクラッシュさせます。問題についてさらに知る最良の方法は、レース検出器の下でプログラムを実行することです。これにより、レースをより確実に識別し、詳細を提供します。

マップは、複雑で自己再編成可能なデータ構造です。同時読み取りおよび書き込みアクセスは未定義です。

コードがなければ、他に言うことはあまりありません。

6
peterSO

マップにintへのポインターを保存し、複数のgoroutineがポイントされているintを読み取り、別のgoroutineがintに新しい値を書き込むことができます。この場合、マップは更新されていません。

これはGoのイディオムではなく、あなたが求めていたものではありません。

または、キーをマップに渡す代わりに、インデックスを配列に渡し、あるゴルーチンでインデックスを更新し、他のゴルーチンでその位置を読み取ることができます。

しかし、キーが既にマップにあるのに、なぜマップの値を新しい値で更新できないのか疑問に思われているでしょう。おそらく、マップのハッシュスキームについては何も変更されていません-少なくとも現在の実装が与えられていません。 Goの作者は、このような特別なケースを許可したくないようです。一般的に、コードは読みやすく、理解しやすく、他のゴルーチンが読み取れる可能性があるときにマップの書き込みを許可しないなどのルールにより、物事が簡単になり、1.6では通常のランタイム中に誤用を見つけ始めることができます-デバッグ。

1
WeakPointer

長い議論の後、マップの典型的な使用には複数のゴルーチンからの安全なアクセスは必要ないと判断されました。そして、そうする場合、マップはおそらくすでに同期されたいくつかのより大きなデータ構造または計算の一部でした。したがって、すべてのマップ操作でミューテックスを取得する必要があると、ほとんどのプログラムの速度が低下し、少数のプログラムに安全性が追加されます。ただし、制御されていないマップアクセスによりプログラムがクラッシュする可能性があるため、これは簡単な決定ではありませんでした。

この言語は、原子マップの更新を妨げません。信頼できないプログラムをホストする場合など、必要な場合、実装はマップアクセスをインターロックできます。

マップアクセスは、更新が発生している場合にのみ安全ではありません。すべてのgoroutinesが読み取りのみ(for rangeループを使用した反復処理を含むマップ内の要素の検索)であり、要素への割り当てや削除を行ってマップを変更しない限り、これらのゴルーチンは同期。

マップの使用を修正するために、言語の一部の実装には、同時実行によってマップが安全に変更されていない場合に実行時に自動的にレポートする特別なチェックが含まれています。

0
I.Tyger

ここでの他の回答が述べているように、ネイティブmapタイプはgoroutine- safeではありません。現在の回答を読んだ後のいくつかのメモ:

  1. ロックを解除するためにdeferを使用しないでください。パフォーマンスに影響するオーバーヘッドがあります( this Nice postを参照)。直接ロック解除を呼び出します。
  2. ロック間の時間を短縮することにより、パフォーマンスを向上させることができます。たとえば、マップを分割します。
  3. concurrent-maphere と呼ばれるこれを解決するために使用される一般的なパッケージ(GitHubで400スターに近づく)があり、パフォーマンスと使いやすさを念頭に置いています。それを使用して、並行性の問題を処理できます。
0
orcaman