同期
Sync
安全に併用できる並行処理を作りたい。
安全でない並行処理から始めて、その動作がシングルスレッド環境で機能することを確認します。
次に、複数のゴルーチンがテストを介してそれを使用して修正することで、安全でないことを実行します。
最初にテストを書く
APIで、カウンターをインクリメントしてその値を取得するメソッドを提供する必要があります。
テストを実行してみます
テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します
Counter
を定義しましょう。
再試行して、次のように失敗します。
最終的にテストを実行するために、これらのメソッドを定義できます。
実行して失敗するはずです。
成功させるのに十分なコードを書く
これは、私たちのようなGoの専門家にとっては簡単なことです。データ型のカウンターの状態を保持し、Inc
を呼び出すたびにインクリメントする必要があります
リファクタリング♪
リファクタリングすることはそれほど多くありませんが、Counter
を中心にさらに多くのテストを作成するので、テストが少し明確に読み取れるように、小さなアサーション関数assertCount
を作成します。
次のステップ
それは十分に簡単でしたが、現在は、並行環境で使用しても安全である必要があるという要件があります。これを実行するには、失敗するテストを作成する必要があります。
最初にテストを書く
これは、wantedCount
をループし、goroutineを起動して、counter.Inc()
を呼び出します。
並行プロセスを同期する便利な方法であるsync.WaitGroup
を使用しています。
WaitGroup
は、ゴルーチンのコレクションが完了するのを待ちます。メインのゴルーチンはAdd
を呼び出して、待機するゴルーチンの数を設定します。次に、各ゴルーチンが実行され、完了したらDone
を呼び出します。同時に、すべてのゴルーチンが完了するまで、Wait
を使用してブロックすることができます。
アサーションを作成する前にwg.Wait()
が完了するのを待つことで、すべてのゴルーチンがCounter
をInc
しようとしたことを確認できます。
テストを実行してみます
テストは別の数値で おそらく 失敗しますが、それでも複数のゴルーチンが同時にカウンターの値を変更しようとしている場合は機能しないことを示しています。
成功させるのに十分なコードを書く
簡単な解決策は、Counter
、Mutex
にロックを追加することです。
Mutex
は相互排他ロックです。ミューテックスのゼロ値は、ロックされていないミューテックスです。
これが意味することは、Inc
を呼び出すgoroutineが最初にある場合、Counter
のロックを取得することです。他のすべてのゴルーチンは、アクセスを取得する前に、それがUnlock
されるのを待つ必要があります。
ここでテストを再実行すると、変更を行う前に各ゴルーチンが順番を待たなければならないため、テストに合格するはずです。
sync.Mutex
が構造体に埋め込まれている他の例を見てきました。
sync.Mutex
が構造体に埋め込まれている他の例を見てきました。あなたはこのような例を見るかもしれません
それはコードをもう少しエレガントにすることができると主張することができます。
これは見栄えが良いですが、プログラミングは非常に主観的な分野ですが、これは悪いことであり間違っています。
場合によっては、型の埋め込みがその型のメソッドが public インターフェースの一部になることを忘れることがあります。そしてあなたはしばしばそれを望まないでしょう。 公開APIには細心の注意を払う必要があることを忘れないでください。何かを公開する瞬間は、他のコードがそれに結合できる瞬間です。私たちは常に不必要な結合を避けたいと思っています。
Lock
とUnlock
を公開することはせいぜい混乱を招きますが、最悪の場合、同じタイプの呼び出し元がこれらのメソッドの呼び出しを開始すると、ソフトウェアに非常に有害な可能性があります。
これは本当に悪い考えのようです
mutexes
のコピー
mutexes
のコピーテストはパスしましたが、コードはまだ少し危険です
コードでgo vet
を実行すると、次のようなエラーが表示されます
sync.Mutex
のドキュメントを見ると理由がわかります
ミューテックスは、最初の使用後にコピーしてはなりません。
Counter
(by value)をassertCounter
に渡すと、ミューテックスのコピーが作成されます。
これを解決するには、代わりにCounter
へのポインターを渡す必要があるため、assertCounter
のシグネチャを変更します
*Counter
ではなくCounter
を渡そうとしているため、テストはコンパイルされなくなりました。これを解決するには、自分で型を初期化しない方がよいことをAPIのリーダーに示すコンストラクタを作成することをお勧めします。
Counter
を初期化するときに、この関数をテストで使用します。
まとめ
同期パッケージ(sync package)のいくつかをカバーしました
Mutex
を使用すると、データにロックを追加できますWaitgroup
は、ゴルーチンがジョブを完了するのを待つ手段です
チャネルとゴルーチンにロックを使用するのはいつですか?
私たちは以前に最初の並行性の章でゴルーチンをカバーしましたこれで安全な並行コードを書くことができるので、なぜロックを使うのでしょうか? go wikiには、このトピック専用のページがあります。ミューテックスまたはチャネル
Goの初心者によくある間違いは、それが可能であったり、楽しいからといって、チャネルやゴルーチンを使いすぎてしまうことです。
sync.Mutex
が問題に最も適している場合は、恐れずに同期を使用してください。 Goは、問題を最もよく解決するツールを使用できるようにし、1つのスタイルのコードに強制するのではなく、実用的です。
言い換え
データの所有権を渡すときにチャネルを使用する
状態の管理にミューテックスを使用する
go vet
ビルドスクリプトでgo vet
を使用することを忘れないでください。貧弱なユーザーに影響が及ぶ前に、コード内のいくつかの微妙なバグを警告することができます。
便利なので埋め込みを使わないでください
埋め込みがパブリックAPIに与える影響について考えてください。
これらのメソッドを 本当に 公開したいですか?
ミューテックスに関しては、これは非常に予測不能で奇妙な方法で潜在的に悲惨なものになる可能性があります。あるべきでないミューテックスをロック解除するいくつかの悪意のあるコードを想像してください。これは非常に奇妙なバグを引き起こし、追跡が困難になります。
最終更新