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