Hone logo
Hone
Problems

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 LockFreeCounter struct 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() and Value() methods must be thread-safe, meaning they can be safely called concurrently from multiple goroutines without data races.
  • The implementation must utilize the sync/atomic package 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 while Increment() 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/atomic package.
  • 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/atomic package provides functions like atomic.AddInt64 for atomic addition and atomic.LoadInt64 for 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.WaitGroup to ensure all goroutines complete before checking the final counter value.
  • Focus on correctness and thread safety first, then optimize for performance if necessary.
Loading editor...
go