Concurrent Counter with Atomic Operations
This challenge focuses on implementing a thread-safe counter using Go's sync/atomic package. Concurrent access to shared resources without proper synchronization can lead to race conditions and incorrect results. This problem will test your understanding of atomic operations and how they can be used to safely manage shared state in a concurrent environment.
Problem Description
You are tasked with creating a ConcurrentCounter struct that encapsulates an integer counter and provides methods for incrementing and retrieving its value. The counter must be thread-safe, meaning multiple goroutines can concurrently increment the counter without causing data corruption or unexpected behavior. You should use the sync/atomic package to ensure atomic operations on the counter's underlying integer value.
Key Requirements:
Increment(): A method that increments the counter by 1 atomically.Value(): A method that returns the current value of the counter atomically.- Thread Safety: The counter must be safe for concurrent access from multiple goroutines. No race conditions are allowed.
Expected Behavior:
Multiple goroutines should be able to call Increment() concurrently, and the final value of the counter should reflect the total number of increments performed by all goroutines. The Value() method should return the most up-to-date value of the counter at the time of the call, without any data races.
Edge Cases to Consider:
- Very high concurrency (many goroutines incrementing the counter simultaneously).
- Rapid incrementing and reading of the counter's value.
- The counter starting at a non-zero value.
Examples
Example 1:
Input: Two goroutines, each calling Increment() 1000 times.
Output: 2000
Explanation: Two goroutines increment the counter concurrently. The final value should be the sum of increments from both goroutines.
Example 2:
Input: One goroutine calling Increment() 500 times, followed by another goroutine calling Increment() 250 times.
Output: 750
Explanation: The second goroutine increments the counter after the first goroutine has already performed some increments.
Example 3:
Input: Multiple goroutines (e.g., 10) calling Increment() a varying number of times (e.g., between 100 and 500). Value() is called from a separate goroutine multiple times during the incrementing process.
Output: The final value of the counter should be the sum of all increments across all goroutines. Value() calls should return consistent, up-to-date values.
Explanation: This tests the thread safety under more complex conditions, ensuring that Value() doesn't return stale data and that increments are correctly accumulated.
Constraints
- The counter's underlying integer type should be
int64. - The
sync/atomicpackage must be used for all operations on the counter's value. - The solution must be efficient and avoid unnecessary locking or synchronization overhead.
- The solution should be well-documented and easy to understand.
Notes
- Consider using
atomic.AddInt64for incrementing the counter. - Consider using
atomic.LoadInt64for retrieving the counter's value. - Think about how to ensure that the
Value()method returns a consistent snapshot of the counter's state, even while other goroutines are incrementing it. The atomic load operation is key here. - Testing with multiple goroutines and a significant number of increments is crucial to verify the thread safety of your solution. Use Go's testing tools to create concurrent tests.