Hone logo
Hone
Problems

Implementing Atomic Operations in Rust

Concurrency is a fundamental aspect of modern software development. When multiple threads access and modify shared data simultaneously, race conditions can lead to unpredictable and incorrect program behavior. This challenge focuses on building safe, concurrent data structures by implementing atomic operations in Rust. You will learn how to leverage Rust's powerful concurrency primitives to ensure that operations on shared memory are indivisible and thread-safe.

Problem Description

Your task is to create a thread-safe counter using Rust's atomic types. A common scenario in concurrent programming is a shared counter that is incremented or decremented by multiple threads. Without proper synchronization, this can lead to lost updates. You will implement this counter using std::sync::atomic types, which provide operations that are guaranteed to execute indivisibly across multiple threads.

Requirements:

  1. AtomicCounter Struct: Define a struct named AtomicCounter that wraps an AtomicUsize.
  2. new() Constructor: Implement an associated function new() that creates and returns an AtomicCounter initialized to zero.
  3. increment() Method: Implement a method increment() that atomically increments the counter by one.
  4. decrement() Method: Implement a method decrement() that atomically decrements the counter by one.
  5. get() Method: Implement a method get() that atomically reads and returns the current value of the counter.

Expected Behavior:

When multiple threads call increment(), decrement(), or get() on the same AtomicCounter instance, the operations should appear to happen in some sequential order, and no updates should be lost. The get() method should always return the latest committed value.

Edge Cases:

  • Overflow/Underflow: For simplicity in this challenge, you do not need to explicitly handle usize overflow or underflow, as Rust's default behavior for AtomicUsize operations is well-defined.

Examples

Example 1: Basic Increment

Imagine you have a counter and two threads each incrementing it 1000 times.

Input:

  • A new AtomicCounter.
  • Two threads.
  • Each thread calls increment() 1000 times.

Output: The final value returned by get() should be 2000.

Explanation: Each increment() operation is atomic. Even though both threads are running concurrently, the system ensures that each increment happens as a single, uninterruptible unit. Therefore, the counter correctly reflects the total number of increments.

Example 2: Mixed Operations

Consider a scenario with multiple threads performing both increments and decrements.

Input:

  • A new AtomicCounter.
  • Three threads.
  • Thread 1 calls increment() 500 times.
  • Thread 2 calls increment() 500 times.
  • Thread 3 calls decrement() 200 times.

Output: The final value returned by get() should be 800 (500 + 500 - 200).

Explanation: The atomic nature of increment() and decrement() ensures that each operation is properly sequenced, preventing race conditions. The final value correctly reflects the net effect of all operations.

Constraints

  • The counter value will be stored in a usize.
  • Input will be through function calls on the AtomicCounter struct.
  • Your solution should be efficient, leveraging the hardware-level atomicity provided by std::sync::atomic. Avoid using locks (like Mutex) for the core atomic operations themselves, as this challenge is specifically about using atomics.

Notes

  • Rust's std::sync::atomic module provides types like AtomicBool, AtomicIsize, AtomicUsize, etc., along with various ordering constraints (Ordering::Relaxed, Ordering::Acquire, Ordering::Release, Ordering::AcqRel, Ordering::SeqCst). For this challenge, you can start with the default SeqCst ordering for simplicity, which provides the strongest guarantees.
  • You will likely need to spawn threads using std::thread::spawn and manage their joining to ensure all operations complete before checking the final value.
  • Consider how to share the AtomicCounter instance across threads safely. A std::sync::Arc (Atomically Reference Counted) is a common and appropriate choice for this.
Loading editor...
rust