Go 同時プログラミングにおけるカウンター同期: ミューテックス、バッファー付きチャネル、およびバッファーなしチャネル
Go で同時アプリケーションを構築する場合、共有データへの安全なアクセスを確保するために同期が重要です。 Mutex
と Channel
は、Go の同期のための主要なツールです。
この記事では、安全な同時実行カウンターを構築するいくつかの方法を検討します。参考記事では Mutex
を使用してこの問題を解決していますが、バッファ付きチャネルとバッファなしチャネルを使用した代替案も検討します。
問題の説明
安全に同時に使用できるカウンターを構築する必要があります。
カウンターコード
<code class="language-go">package main type Counter struct { count int } func (c *Counter) Inc() { c.count++ } func (c *Counter) Value() int { return c.count }</code>
コードの同時実行性を安全にするために、いくつかのテストを書いてみましょう。
1. ミューテックスを使用します
Mutex
(ミューテックス) は、一度に 1 つのゴルーチンだけがコードの重要な部分にアクセスできるようにする同期プリミティブです。これはロックメカニズムを提供します。ゴルーチンが Mutex
をロックすると、それをロックしようとする他のゴルーチンは Mutex
がロック解除されるまでブロックされます。したがって、共有変数またはリソースを競合状態から保護する必要がある場合によく使用されます。
<code class="language-go">package main import ( "sync" "testing" ) func TestCounter(t *testing.T) { t.Run("using mutexes and wait groups", func(t *testing.T) { counter := Counter{} wantedCount := 1000 var wg sync.WaitGroup var mut sync.Mutex wg.Add(wantedCount) for i := 0; i < wantedCount; i++ { go func() { defer wg.Done() mut.Lock() counter.Inc() mut.Unlock() }() } wg.Wait() if counter.Value() != wantedCount { t.Errorf("got %d, want %d", counter.Value(), wantedCount) } }) }</code>
このコードは、sync.WaitGroup
を使用してすべてのゴルーチンの完了を追跡し、sync.Mutex
を使用して複数のゴルーチンが共有カウンターに同時にアクセスすることを防ぎます。
2. バッファチャネルを使用します
チャネルは、Go が goroutine が安全に通信できるようにする方法です。これらはゴルーチン間でデータを転送し、渡されたデータへのアクセスを制御することで同期を提供できます。
この例では、チャネルを使用して goroutine をブロックし、1 つの goroutine のみが共有データにアクセスできるようにします。バッファリングされたチャネルの容量は固定されており、送信者をブロックする前に事前定義された数の要素を保持できることを意味します。送信者はバッファがいっぱいの場合にのみブロックします。
<code class="language-go">package main import ( "sync" "testing" ) func TestCounter(t *testing.T) { t.Run("using buffered channels and wait groups", func(t *testing.T) { counter := Counter{} wantedCount := 1000 var wg sync.WaitGroup wg.Add(wantedCount) ch := make(chan struct{}, 1) ch <- struct{}{} // 允许第一个 goroutine 开始 for i := 0; i < wantedCount; i++ { go func() { defer wg.Done() <-ch counter.Inc() ch <- struct{}{} }() } wg.Wait() if counter.Value() != wantedCount { t.Errorf("got %d, want %d", counter.Value(), wantedCount) } }) }</code>
このコードは容量 1 のバッファリングされたチャネルを使用し、一度に 1 つの goroutine のみがカウンターにアクセスできるようにします。
3. バッファリングされていないチャネルを使用する
バッファなしチャンネルにはバッファがありません。受信側がデータを受信する準備ができるまで、送信側をブロックします。これにより、ゴルーチン間で一度に 1 つずつデータが受け渡される厳密な同期が提供されます。
<code class="language-go">package main import ( "sync" "testing" ) func TestCounter(t *testing.T) { t.Run("using unbuffered channels and wait groups", func(t *testing.T) { counter := Counter{} wantedCount := 1000 var wg sync.WaitGroup wg.Add(wantedCount) ch := make(chan struct{}) go func() { for i := 0; i < wantedCount; i++ { ch <- struct{}{} } close(ch) }() for range ch { counter.Inc() wg.Done() } if counter.Value() != wantedCount { t.Errorf("got %d, want %d", counter.Value(), wantedCount) } }) } </code>
このコードはバッファリングされていないチャネルを使用して、一度に 1 つの goroutine だけがカウンターにアクセスできるようにします。
4. WaitGroup の代わりにバッファ チャネルを使用します
WaitGroup
なしでバッファリングされたチャネルを使用することもできます。たとえば、無限ループや別のチャネルを使用してゴルーチンの完了を追跡することができます。
結論
この記事では、Go で安全な同時実行カウンターを構築するためのさまざまなアプローチを検討します。これらのツールとそれらをいつ使用するかを理解することが、効率的で安全な同時実行 Go プログラムを作成するための鍵となります。
参考リソース
この記事は、「テストで Go を学ぶ」の同期の章からインスピレーションを受けています。
この記事がお役に立てば幸いです!
以上がGo 同時実行性: ミューテックスとチャネルの例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。