In Go, handling concurrent access to shared data primarily involves two common methods: using Mutex and using Channels. Below, I will explain these two methods in detail, along with examples.
1. Using Mutex
A Mutex is a synchronization mechanism used to prevent multiple goroutines from accessing shared data concurrently. The sync package in Go's standard library provides the Mutex type for this purpose.
Example:
Consider a shared account balance where multiple goroutines attempt to update it simultaneously.
gopackage main import ( "fmt" "sync" "time" ) type Account struct { balance int mu sync.Mutex } func (a *Account) Deposit(amount int) { a.mu.Lock() // Locking defer a.mu.Unlock() // Ensuring unlock with defer a.balance += amount } func main() { var wg sync.WaitGroup account := &Account{balance: 100} // Simulating multiple goroutines depositing simultaneously for i := 0; i < 5; i++ { wg.Add(1) go func(amount int) { defer wg.Done() account.Deposit(amount) }(100) } wg.Wait() fmt.Println("Final balance:", account.balance) }
In this example, we use Mutex to control access to balance, ensuring that only one goroutine can modify the balance at a time.
2. Using Channels
Channels are a core feature in Go for passing messages between goroutines. By using channels, we can avoid explicit locks and handle concurrency in a more idiomatic Go way.
Example:
We can create a dedicated goroutine for updating the account balance, receiving update commands through channels.
gopackage main import ( "fmt" "sync" ) type operation struct { amount int balance chan int } func main() { var wg sync.WaitGroup opChan := make(chan operation, 5) balance := 100 // Starting a goroutine to manage the balance go func() { for op := range opChan { balance += op.amount op.balance <- balance } }() // Sending operations to the goroutine for i := 0; i < 5; i++ { wg.Add(1) go func(amount int) { defer wg.Done() balanceChan := make(chan int) opChan <- operation{amount, balanceChan} newBalance := <-balanceChan fmt.Println("New balance:", newBalance) }(100) } wg.Wait() close(opChan) // Closing the channel to end the balance management goroutine }
In this example, we define an operation type that includes the amount and a channel to return the new balance. A separate goroutine listens to this channel, processes all balance updates, and returns the new balance through another channel. This approach avoids direct concurrent access to shared resources.
Summary
When handling concurrent access to shared data in Go, it is recommended to select the appropriate synchronization mechanism based on the specific context. For simple data protection, Mutex is a good choice. When dealing with complex states or coordinating multiple resources, channels combined with goroutines provide greater flexibility and better scalability.