Hone logo
Hone
Problems

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:

  • Counter Struct: Define a Counter struct containing a single int64 field named value.
  • Increment() Method: A method on the Counter struct that atomically increments the value by 1.
  • Decrement() Method: A method on the Counter struct that atomically decrements the value by 1.
  • Thread Safety: The Increment() and Decrement() methods must use atomic operations from the sync/atomic package to ensure thread safety. Directly modifying the value field 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 value field in the Counter struct must be of type int64.
  • The Increment() and Decrement() methods must use functions from the sync/atomic package (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/atomic package provides functions for performing atomic operations on primitive types.
  • Consider using atomic.AddInt64 for 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 value field is not sufficient.
  • Testing with multiple goroutines and a significant number of operations is crucial to verify the correctness of your implementation.
Loading editor...
go