Concurrent Counter with Multiple Writers and Readers
This challenge focuses on implementing a thread-safe counter using Go's synchronization primitives. Understanding and correctly utilizing mutexes and read/write mutexes is crucial for building robust concurrent applications. You'll be tasked with creating a counter that can be safely incremented by multiple goroutines and read by others, ensuring data integrity.
Problem Description
You need to implement a Counter struct that provides thread-safe increment and get operations. The Counter should be initialized with a starting value of 0. Multiple goroutines should be able to concurrently increment the counter, while other goroutines can safely read the current value. The implementation must prevent race conditions and ensure that reads always reflect the latest value after an increment.
Key Requirements:
- Thread-Safe Increment: The
Increment()method should atomically increment the counter's value. Multiple goroutines callingIncrement()concurrently should not lead to lost updates. - Thread-Safe Get: The
Get()method should return the current value of the counter without data races. - Read/Write Mutex Optimization: Use a
sync.RWMutexto optimize read operations. Multiple readers should be able to access the counter concurrently, while writers (incrementing) should have exclusive access. - Initialization: The
Counterstruct should be initialized with a starting value of 0.
Expected Behavior:
- Multiple goroutines incrementing the counter concurrently should result in the counter's value accurately reflecting the total number of increments.
- Reading the counter's value should always return a consistent and up-to-date value, even when increments are happening concurrently.
- The program should not panic due to race conditions.
Edge Cases to Consider:
- High concurrency: Simulate a large number of goroutines incrementing and reading the counter simultaneously.
- Rapid increments followed by reads: Test the scenario where increments happen very quickly, followed by a read operation.
Examples
Example 1:
Input: Multiple goroutines concurrently calling Increment() 1000 times each, followed by a Get() call. Let's say 5 goroutines each call Increment() 1000 times.
Output: 5000
Explanation: Each of the 5 goroutines increments the counter 1000 times, resulting in a total of 5000 increments. The Get() call should return this value.
Example 2:
Input: One goroutine increments the counter 100 times, another goroutine increments it 50 times, and then a third goroutine calls Get().
Output: 150
Explanation: The first goroutine increments the counter 100 times, and the second goroutine increments it 50 times. The Get() call should return the sum of these increments, which is 150.
Example 3: (Edge Case - High Concurrency)
Input: 100 goroutines concurrently calling Increment() 1000 times each, followed by a Get() call.
Output: 100000
Explanation: Each of the 100 goroutines increments the counter 1000 times, resulting in a total of 100,000 increments. The Get() call should return this value, demonstrating the thread-safe nature of the implementation under high load.
Constraints
- The
Counterstruct must be defined. - The
Increment()method must take no arguments and return nothing. - The
Get()method must take no arguments and return anint. - The implementation must use a
sync.RWMutex. - The program should be able to handle at least 1000 concurrent increment operations without significant performance degradation.
- The counter's initial value must be 0.
Notes
- Consider using
sync.WaitGroupto wait for all incrementing goroutines to complete before callingGet(). This will help ensure that all increments are reflected in the final value. - The
sync.RWMutexallows multiple readers or a single writer. Think about how to leverage this for optimal performance. - Thorough testing with concurrent goroutines is essential to verify the correctness of your implementation. Use
go test -raceto detect potential race conditions.