Atomic Fetch Operations

Atomic fetch operations are a set of operations that combine reading a value from a shared variable and modifying it in a single, indivisible step. These operations guarantee that the read-modify-write sequence is executed atomically, preventing race conditions and ensuring data consistency.

Some common atomic fetch operations include:

  • Fetch and Add: Atomically reads the value of a variable and adds a specified value to it.
  • Fetch and Subtract: Atomically reads the value of a variable and subtracts a specified value from it.
  • Fetch and Or: Atomically reads the value of a variable and performs a bitwise OR operation with a specified value.
  • Fetch and And: Atomically reads the value of a variable and performs a bitwise AND operation with a specified value.

Here’s an example of using the AtomicInteger class in Java to perform an atomic fetch and add operation:

AtomicInteger counter = new AtomicInteger(0);

// Atomically increment the counter and get the previous value
int previousValue = counter.getAndIncrement();

In this example, the getAndIncrement() method atomically increments the counter variable and returns its previous value.

Compare and Swap (CAS)

Compare and Swap (CAS) is another fundamental atomic operation used in concurrent programming. CAS allows a thread to atomically compare the value of a shared variable with an expected value and, if they match, update the variable with a new value.

Here’s an example of using CAS in Java to implement a lock-free stack:

public class LockFreeStack<T> {
    private AtomicReference<Node<T>> top = new AtomicReference<>();

    public void push(T value) {
        Node<T> newNode = new Node<>(value);
        Node<T> oldTop;
        do {
            oldTop = top.get();
            newNode.next = oldTop;
        } while (!top.compareAndSet(oldTop, newNode));
    }

    public T pop() {
        Node<T> oldTop;
        Node<T> newTop;
        do {
            oldTop = top.get();
            if (oldTop == null) {
                return null;
            }
            newTop = oldTop.next;
        } while (!top.compareAndSet(oldTop, newTop));
        return oldTop.value;
    }

    private static class Node<T> {
        private T value;
        private Node<T> next;

        // Constructor and getter/setter methods
    }
}

In this example, the push() and pop() methods use CAS operations to atomically update the top reference of the stack. The compareAndSet() method compares the current value of top with the expected value (oldTop) and, if they match, updates top with the new value (newNode or newTop).

Fetch Operations in Different Programming Languages

Fetch operations and atomic primitives are available in various programming languages, often as part of their standard libraries or through concurrent programming frameworks.

In Java, the java.util.concurrent.atomic package provides classes like AtomicInteger, AtomicLong, AtomicReference, and others that offer atomic fetch operations and CAS.

In Rust, the std::sync::atomic module offers atomic types and operations, such as AtomicUsize, AtomicBool, and AtomicPtr, which provide fetch and modify operations like fetch_add(), fetch_sub(), fetch_or(), and fetch_and().

By leveraging atomic fetch operations and CAS, developers can build lock-free data structures and algorithms that offer high performance and scalability in concurrent environments.

Understanding and utilizing fetch operations is crucial for writing correct and efficient concurrent programs. With the support of atomic primitives in modern programming languages, developers have the tools to tackle the challenges of concurrency and build robust concurrent systems.