Home >Backend Development >Golang >Go Concurrency: Mutexes vs Channels with Examples
Counter synchronization in Go concurrent programming: Mutex, buffered channels and unbuffered channels
When building concurrent applications in Go, synchronization is crucial to ensure secure access to shared data. Mutex
and Channel
are the main tools for synchronization in Go.
This article explores several ways to build safe concurrency counters. While the reference article solves this problem using Mutex
, we'll also explore alternatives using buffered and unbuffered channels.
Problem Description
We need to build a counter that can be safely used concurrently.
Counter code
<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>
To make our code concurrency safe, let’s write some tests.
1. Use Mutex
Mutex
(mutex) is a synchronization primitive that ensures that only one goroutine can access critical parts of the code at a time. It provides a locking mechanism: when a goroutine locks Mutex
, other goroutines trying to lock it will be blocked until Mutex
is unlocked. Therefore, it is often used when a shared variable or resource needs to be protected from race conditions.
<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>
The code uses sync.WaitGroup
to track the completion of all goroutines, and uses sync.Mutex
to prevent multiple goroutines from accessing the shared counter at the same time.
2. Use buffer channel
Channels are a way for Go to allow goroutines to communicate securely. They are able to transfer data between goroutines and provide synchronization by controlling access to the passed data.
In this example, we will use channels to block goroutines and allow only one goroutine to access the shared data. Buffered channels have a fixed capacity, meaning they can hold a predefined number of elements before blocking the sender. The sender will only block when the buffer is full.
<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>
The code uses a buffered channel with a capacity of 1, allowing only one goroutine to access the counter at a time.
3. Use non-buffered channels
Unbuffered channels have no buffers. They block the sender until the receiver is ready to receive data. This provides strict synchronization, where data is passed between goroutines one at a time.
<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>
The code uses unbuffered channels to ensure that only one goroutine accesses the counter at a time.
4. Use buffer channel instead of WaitGroup
We can also use buffered channels without WaitGroup
, for example using an infinite loop or another channel to track the completion of the goroutine.
Conclusion
This article explores different approaches to building safe concurrency counters in Go. Knowing these tools and when to use them is key to writing efficient and safe concurrent Go programs.
Reference Resources
This article is inspired by the synchronization chapter in "Learn Go with tests".
Hope this article is helpful to you!
The above is the detailed content of Go Concurrency: Mutexes vs Channels with Examples. For more information, please follow other related articles on the PHP Chinese website!