Lock-Free Counter in Go
Lock-free algorithms are crucial for building highly concurrent and responsive systems, especially when dealing with shared resources. This challenge asks you to implement a lock-free counter in Go using atomic operations. A lock-free counter allows multiple goroutines to increment the counter concurrently without the need for traditional locks, minimizing contention and improving performance.
Problem Description
You are tasked with creating a LockFreeCounter struct in Go that provides a thread-safe way to increment and retrieve the current value of a counter without using mutexes or other locking mechanisms. The counter should be implemented using atomic operations provided by the sync/atomic package.
What needs to be achieved:
- Create a
LockFreeCounterstruct that encapsulates an integer value. - Implement an
Increment()method that atomically increments the counter's value. - Implement a
Value()method that returns the current value of the counter.
Key Requirements:
- The
Increment()andValue()methods must be thread-safe, meaning they can be safely called concurrently from multiple goroutines without data races. - The implementation must utilize the
sync/atomicpackage for atomic operations. - The counter should be initialized to 0.
Expected Behavior:
Multiple goroutines should be able to concurrently call Increment() on the same LockFreeCounter instance, and the Value() method should return the correct, accumulated count.
Edge Cases to Consider:
- High concurrency: Test with a large number of goroutines incrementing the counter simultaneously.
- Rapid incrementation: Test with goroutines incrementing the counter very frequently.
- Value() called concurrently with Increment(): Ensure
Value()returns a consistent value even whileIncrement()is in progress.
Examples
Example 1:
Input: Two goroutines, each incrementing the counter 1000 times.
Output: The counter value should be 2000.
Explanation: Each goroutine increments the counter 1000 times, so the final value should be the sum of their increments.
Example 2:
Input: Five goroutines, each incrementing the counter 500 times concurrently.
Output: The counter value should be 2500.
Explanation: Five goroutines each incrementing 500 times results in a total of 2500 increments.
Example 3: (Edge Case)
Input: One goroutine increments the counter 1000 times while another goroutine repeatedly calls Value() concurrently.
Output: Value() should consistently return a value that monotonically increases, eventually reaching 1000.
Explanation: Demonstrates that Value() doesn't race with Increment() and returns a consistent view of the counter.
Constraints
- The implementation must use the
sync/atomicpackage. - No mutexes or other locking mechanisms are allowed.
- The counter's initial value must be 0.
- The
Increment()method should be as efficient as possible. - The
Value()method should return the most up-to-date value available.
Notes
- The
sync/atomicpackage provides functions likeatomic.AddInt64for atomic addition andatomic.LoadInt64for atomic loading of integer values. - Consider the memory model implications of atomic operations in Go.
- Thorough testing with concurrent goroutines is essential to verify the correctness of the implementation. Use
sync.WaitGroupto ensure all goroutines complete before checking the final counter value. - Focus on correctness and thread safety first, then optimize for performance if necessary.