Ответ
std::mutex — это примитив блокирующей синхронизации. Он обеспечивает эксклюзивный доступ к целой критической секции кода. Если мьютекс захвачен другим потоком, текущий поток блокируется (переходит в состояние ожидания).
std::atomic<T> — это шаблонный тип, обеспечивающий безблокирующую (lock-free или wait-free) синхронизацию для отдельных операций над переменной. Атомарные операции гарантированно выполняются как единое целое с точки зрения других потоков.
Ключевые отличия:
- Гранулярность: Mutex защищает произвольный блок кода (критическую секцию).
std::atomicзащищает доступ к одной конкретной переменной. - Производительность: Атомарные операции, как правило, значительно быстрее, так как реализуются с помощью атомарных инструкций процессора (например,
LOCK CMPXCHGна x86) и не требуют переключения контекста. Mutex involves system calls and context switches when contention occurs. - Применимость: Mutex можно использовать для синхронизации доступа к сложным структурам данных или для выполнения последовательности операций (транзакции).
std::atomicподходит для простых операций: чтение/запись, инкремент, сравнение с обменом (CAS).
Пример: потокобезопасный счётчик.
С использованием std::mutex (избыточно для одной операции):
#include <mutex>
class CounterMutex {
int value = 0;
mutable std::mutex mtx;
public:
void increment() {
std::lock_guard<std::mutex> lock(mtx); // Блокировка
++value; // Неатомарная операция под защитой мьютекса
} // Автоматическая разблокировка
int get() const {
std::lock_guard<std::mutex> lock(mtx);
return value;
}
};
С использованием std::atomic (оптимально):
#include <atomic>
class CounterAtomic {
std::atomic<int> value{0};
public:
void increment() {
// Атомарная операция fetch_add. Никаких блокировок.
value.fetch_add(1, std::memory_order_relaxed);
}
int get() const {
// Атомарное чтение.
return value.load(std::memory_order_acquire);
}
};
Пример, где mutex необходим, а atomic недостаточен:
// Нужно атомарно увеличить два счётчика.
// С atomic это две отдельные операции, между которыми другой поток может вмешаться.
std::atomic<int> cnt1{0}, cnt2{0};
// НЕПРАВИЛЬНО: состояние (cnt1, cnt2) может быть несогласованным для внешнего наблюдателя.
cnt1++;
cnt2++;
// ПРАВИЛЬНО: используем mutex для атомарности составной операции.
std::mutex mtx;
mtx.lock();
cnt1++;
cnt2++;
mtx.unlock();
Правило выбора: Используйте std::atomic для простых операций над одиночными переменными (флаги, счётчики, указатели). Используйте std::mutex (или другие мьютексы) когда нужно защитить доступ к нескольким переменным одновременно или выполнить последовательность операций как единое целое.