Concurrent Counter with Read-Write Mutex
This challenge involves building a thread-safe counter in Go that can be accessed concurrently by multiple goroutines. You'll learn how to use sync.RWMutex to efficiently handle both read and write operations, optimizing performance when reads are more frequent than writes.
Problem Description
You need to implement a ConcurrentCounter struct that safely manages an integer counter. This counter will be accessed by multiple goroutines simultaneously. Some goroutines will increment the counter (write operations), while others will read its current value (read operations). To ensure data integrity and avoid race conditions, you must use a sync.RWMutex to protect the counter's value.
Key Requirements:
ConcurrentCounterstruct: Define a struct namedConcurrentCounterthat includes an integer field to hold the counter's value and async.RWMutexfield for synchronization.Increment()method: Implement a methodIncrement()that atomically increases the counter by 1. This method should use the read-write mutex for exclusive write access.GetValue()method: Implement a methodGetValue()that returns the current value of the counter. This method should use the read-write mutex to allow multiple concurrent readers but block writers.- Thread Safety: The
ConcurrentCountermust be safe to use by multiple goroutines concurrently.
Expected Behavior:
When multiple goroutines call Increment() and GetValue() on the same ConcurrentCounter instance, the counter's value should always be accurate, and no data corruption should occur. Reads should not block other reads, but they should block writes, and writes should block all other operations (reads and writes).
Edge Cases:
- What happens if
GetValue()is called precisely whenIncrement()is modifying the counter? TheRWMutexshould ensure the read operation either sees the value before the increment or after the increment is fully completed.
Examples
Example 1:
- Input:
- A
ConcurrentCounterinstance. - 10 goroutines calling
Increment()100 times each. - 2 goroutines calling
GetValue()periodically.
- A
- Output:
- The final value of the counter should be 1000.
- The values read by
GetValue()should be consistent with the increments performed up to that point.
- Explanation: Ten goroutines each add 100 to the counter, resulting in a total of 1000. The
RWMutexensures that each increment operation is atomic and that reads do not interfere with writes or vice versa.
Example 2:
- Input:
- A
ConcurrentCounterinstance initialized to 0. - Goroutine A calls
Increment()5 times. - Goroutine B calls
GetValue()3 times concurrently with Goroutine A. - Goroutine C calls
Increment()5 times concurrently with Goroutine A and B.
- A
- Output:
- The final value of the counter will be 10.
- The values returned by Goroutine B's
GetValue()calls will be within the range of [0, 10], reflecting the state of the counter at the moment each read occurred.
- Explanation: Even with interleaved read and write operations, the
RWMutexguarantees that the final count is correct and that individual reads reflect a consistent state.
Constraints
- The counter value will not exceed the maximum value of an
int. - The number of concurrent goroutines accessing the counter can be large, but it will not exceed 1000.
- The number of operations per goroutine will not exceed 10,000.
- The solution must be written in Go.
Notes
sync.RWMutexprovides two locking mechanisms:Lock()/Unlock()for exclusive write access, andRLock()/RUnlock()for shared read access.- Consider the order of operations when implementing
Increment()andGetValue(). - Think about how to ensure that
GetValue()doesn't read a partially updated value. - You'll likely need to use
sync.WaitGroupto coordinate the goroutines in your testing or demonstration.