Implementing Acquire-Release Semantics with Mutexes in Go
Concurrency control is fundamental in modern software development. Understanding how to manage shared resources safely between multiple goroutines is crucial. This challenge focuses on implementing acquire-release semantics using Go's built-in sync.Mutex, a common tool for mutual exclusion. You will simulate a scenario where multiple goroutines try to access and modify a shared resource, and you need to ensure data integrity using the acquire-release pattern.
Problem Description
Your task is to implement a mechanism that simulates acquire-release semantics for a shared integer counter using Go's sync.Mutex. Acquire-release semantics guarantee that any writes performed by a goroutine before it releases a lock are visible to any other goroutine that subsequently acquires the same lock.
You will create a SafeCounter struct that encapsulates an integer value and a sync.Mutex. You need to implement two methods: Increment and GetValue.
Key Requirements:
Increment(): This method should atomically increment thevalueby 1. It must acquire the mutex before modifying thevalueand release it afterward.GetValue(): This method should return the currentvalueof the counter. It must also acquire the mutex before reading thevalueand release it afterward to ensure it reads the most up-to-date value.
Expected Behavior:
When multiple goroutines concurrently call Increment, the final value of the SafeCounter should be exactly the total number of times Increment was called across all goroutines. This demonstrates that the acquire-release pattern correctly synchronizes access and prevents race conditions.
Edge Cases:
- Zero calls: If
Incrementis never called,GetValueshould return 0. - High concurrency: The solution should behave correctly even when a large number of goroutines are calling
Incrementsimultaneously.
Examples
Example 1:
Input:
Goroutines: 10
Increments per goroutine: 1000
Expected Output:
Final Counter Value: 10000
Explanation: 10 goroutines each increment the counter 1000 times. With proper acquire-release semantics, the final value should be the sum of all increments: 10 * 1000 = 10000.
Example 2:
Input:
Goroutines: 5
Increments per goroutine: 500
Expected Output:
Final Counter Value: 2500
Explanation: 5 goroutines each increment the counter 500 times. The total expected value is 5 * 500 = 2500.
Constraints
- The
SafeCounterstruct must contain async.Mutex. - The
IncrementandGetValuemethods must use the mutex to protect access to thevalue. - The solution should be written in Go.
- The number of goroutines and increments per goroutine can be up to 1000 each in testing scenarios.
Notes
- The
sync.Mutexin Go provides mutual exclusion. CallingLock()on a mutex blocks until the mutex is unlocked. CallingUnlock()releases the mutex. - Acquire-release semantics are inherently provided by the
Lock()andUnlock()operations of async.Mutex. Any write that happens betweenLock()andUnlock()is guaranteed to be visible to any goroutine that subsequently acquires the lock. - Consider using
sync.WaitGroupto manage the lifecycle of your goroutines and ensure the main program waits for all increments to complete before checking the final value.