Concurrent Counter with Atomic Operations
This challenge focuses on implementing a thread-safe counter using Go's atomic operations. Understanding and utilizing atomic operations is crucial for writing correct and efficient concurrent programs, preventing race conditions when multiple goroutines access and modify shared data. You'll be building a simple counter that can be incremented and decremented concurrently without data corruption.
Problem Description
You are tasked with creating a Counter struct that encapsulates an integer value and provides methods for incrementing and decrementing this value atomically. The Counter struct should use sync/atomic package to ensure thread-safe operations. Your implementation must guarantee that incrementing and decrementing the counter from multiple goroutines simultaneously will produce the correct final value, without any race conditions.
Key Requirements:
CounterStruct: Define aCounterstruct containing a singleint64field namedvalue.Increment()Method: A method on theCounterstruct that atomically increments thevalueby 1.Decrement()Method: A method on theCounterstruct that atomically decrements thevalueby 1.- Thread Safety: The
Increment()andDecrement()methods must use atomic operations from thesync/atomicpackage to ensure thread safety. Directly modifying thevaluefield without atomic operations will result in incorrect behavior.
Expected Behavior:
Multiple goroutines should be able to concurrently call Increment() and Decrement() on the same Counter instance without causing data corruption. The final value of the counter should reflect the net effect of all increment and decrement operations.
Edge Cases to Consider:
- High Concurrency: Test with a large number of goroutines incrementing and decrementing the counter simultaneously.
- Interleaved Operations: Ensure that increments and decrements from different goroutines are properly interleaved without losing updates.
- Zero Value: The counter should correctly handle starting from a zero value.
Examples
Example 1:
Input: Two goroutines, each incrementing a Counter 1000 times.
Output: Counter value: 2000
Explanation: Two goroutines concurrently increment the counter. The final value should be the sum of increments from both goroutines.
Example 2:
Input: One goroutine increments a Counter 500 times, another decrements it 250 times.
Output: Counter value: 250
Explanation: The net effect is 500 increments and 250 decrements, resulting in a final value of 250.
Example 3: (Edge Case)
Input: 100 goroutines, each incrementing a Counter 1000 times, followed by 50 goroutines, each decrementing the Counter 500 times.
Output: Counter value: 85000
Explanation: 100 * 1000 increments = 100000. 50 * 500 decrements = 25000. 100000 - 25000 = 85000.
Constraints
- The
valuefield in theCounterstruct must be of typeint64. - The
Increment()andDecrement()methods must use functions from thesync/atomicpackage (e.g.,atomic.AddInt64,atomic.LoadInt64,atomic.StoreInt64). - The solution must be written in Go.
- The solution should be reasonably efficient. While performance is not the primary focus, avoid unnecessary overhead.
Notes
- The
sync/atomicpackage provides functions for performing atomic operations on primitive types. - Consider using
atomic.AddInt64for incrementing and decrementing the counter. This function atomically adds a delta to the counter's value. - Think about how to ensure that the increment and decrement operations are truly atomic, preventing race conditions. Directly modifying the
valuefield is not sufficient. - Testing with multiple goroutines and a significant number of operations is crucial to verify the correctness of your implementation.