Logo Craft Homelab Docs Контакты Telegram
Lock-free структуры данных — атомарные операции и CAS
Mon Dec 29 2025

Lock-free структуры: многопоточность без блокировок

Lock-free структуры данных обеспечивают высокую производительность в многопоточных приложениях. В отличие от традиционных структур с мьютексами, lock-free алгоритмы используют атомарные операции для избежания блокировок. Это особенно важно для высоконагруженных систем.

Атомарные операции

Атомарные операции выполняются целиком без прерывания. Это основа lock-free алгоритмов.

Compare-And-Swap (CAS)

CAS — фундаментальная операция для lock-free структур. Она сравнивает значение с ожидаемым и, если они равны, заменяет на новое.

// Псевдокод CAS
bool CAS(int* ptr, int old_val, int new_val) {
    if (*ptr == old_val) {
        *ptr = new_val;
        return true;
    }
    return false;
}

Операция атомарна — между сравнением и заменой другой поток не может изменить значение.

Атомарные типы в C++

#include <atomic>

std::atomic<int> counter{0};

// Атомарные операции
counter.fetch_add(1);        // Атомарное +=
counter.fetch_sub(1);        // Атомарное -=
counter.compare_exchange(expected, desired);  // CAS
bool was = counter.is_lock_free();

Lock-free стек

Lock-free стек использует CAS для атомарного добавления и удаления элементов. Основная идея — обновлять указатель head только если он не изменился:

#include <atomic>

template<typename T>
class LockFreeStack {
private:
    struct Node {
        T data;
        Node* next;
    };
    
    std::atomic<Node*> head;
    
public:
    void push(T value) {
        Node* newNode = new Node();
        newNode->data = value;
        newNode->next = head.load();
        
        // CAS до тех пор, пока head не изменится
        while (!head.compare_exchange_weak(newNode->next, newNode)) {
            // newNode->next обновлён другим потоком
        }
    }
    
    bool pop(T& result) {
        Node* oldHead = head.load();
        
        while (oldHead != nullptr) {
            Node* next = oldHead->next;
            if (head.compare_exchange_weak(oldHead, next)) {
                result = oldHead->data;
                delete oldHead;
                return true;
            }
        }
        return false;
    }
};

Lock-free очередь

#include <atomic>

template<typename T>
class LockFreeQueue {
private:
    struct Node {
        T data;
        std::atomic<Node*> next;
    };
    
    std::atomic<Node*> head;
    std::atomic<Node*> tail;
    
public:
    LockFreeQueue() {
        Node* dummy = new Node();
        dummy->next = nullptr;
        head = tail = dummy;
    }
    
    void enqueue(T value) {
        Node* newNode = new Node();
        newNode->data = value;
        newNode->next = nullptr;
        
        Node* oldTail = tail.load();
        while (!tail.compare_exchange_weak(oldTail, newNode)) {
            // tail изменился, пробуем снова
        }
        oldTail->next.store(newNode);
    }
    
    bool dequeue(T& result) {
        Node* oldHead = head.load();
        
        while (oldHead != nullptr) {
            Node* next = oldHead->next.load();
            
            if (next == nullptr) {
                return false;  // Очередь пуста
            }
            
            if (head.compare_exchange_weak(oldHead, next)) {
                result = next->data;
                delete oldHead;
                return true;
            }
        }
        return false;
    }
};

ABA problem

// Проблема: поток A видит значение X
// поток B меняет X на Y, потом обратно на X
// поток A думает, что X не изменилось

// Решение: использовать двойной CAS или теги
struct TaggedPointer {
    void* ptr;
    uint64_t tag;
};

std::atomic<TaggedPointer> ptr;

bool compare_exchange(TaggedPointer& expected, TaggedPointer desired) {
    TaggedPointer current = ptr.load();
    if (current.ptr == expected.ptr && current.tag == expected.tag) {
        ptr.store(desired);
        return true;
    }
    expected = current;
    return false;
}

Атомарные операции в Go

import "sync/atomic"

var counter int64

// Атомарное сложение
atomic.AddInt64(&counter, 1)

// Атомарная загрузка
value := atomic.LoadInt64(&counter)

// CAS
for {
    old := atomic.LoadInt64(&counter)
    if atomic.CompareAndSwapInt64(&counter, old, old+1) {
        break
    }
}

Атомарные операции в Java

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

AtomicInteger counter = new AtomicInteger(0);

// Атомарные операции
counter.incrementAndGet();
counter.getAndIncrement();
counter.compareAndSet(5, 10);

Memory ordering

#include <atomic>

std::atomic<int> x{0};
int y;

// Sequential consistency (по умолчанию)
x.store(1, std::memory_order_seq_cst);
y = x.load(std::memory_order_seq_cst);

// Relaxed - нет гарантий порядка
x.store(1, std::memory_order_relaxed);

// Acquire-release
// writer
x.store(1, std::memory_order_release);
// reader
y = x.load(std::memory_order_acquire);

Применение

  • Высокопроизводительные очереди сообщений
  • Non-blocking алгоритмы
  • Реализация spinlocks
  • Структуры данных с высокой конкуренцией

Заключение

Lock-free структуры — инструмент для экстремальной производительности, требует глубокого понимания многопоточности.