MiddleКодИногдаЕщё не отвечали
Напишите базовую реализацию std::atomic<T>.
Реализуйте минимальную обёртку Atomic<T> с операциями load, store, exchange и compare_exchange_strong.
Требования:
- защитите значение так, чтобы каждая операция была атомарной;
compare_exchange_strongдолжен сравнивать-и-обменивать одним неделимым шагом (load, а затем отдельный условныйstore— не атомарны) - при неудаче
compare_exchange_strongзапишите текущее значение обратно вexpected - сделайте обёртку некопируемой
- версия на мьютексе — дидактическая база; настоящий
std::atomic<T>для тривиально-копируемых типов использует lock-free инструкции CPU, а не мьютекс
template<typename T>
class Atomic {
public:
Atomic();
explicit Atomic(T val);
T load() const;
void store(T desired);
T exchange(T desired);
bool compare_exchange_strong(T& expected, T desired);
// ваш код здесь
};
Допишите реализацию.
Минимальная Atomic<T> оборачивает значение и мьютекс с load/store/compare_exchange_strong. Настоящий std::atomic<T> для тривиально-копируемых типов использует lock-free инструкции CPU через intrinsic — без мьютекса.
- ✗Забывать защищать
compare_exchange_strongатомарно — загрузка + условное сохранение с промежутком между ними не атомарны и разрушают смысл операции - ✗Использовать
volatileвместо атомиков для межпотоковой коммуникации —volatileпредотвращает переупорядочивание компилятором, но не CPU - ✗Опускать параметры memory_order — по умолчанию
memory_order_seq_cstкорректен, но самый дорогой; для простых флагов достаточноmemory_order_acquire/release
- →Как CPU гарантирует атомарность 64-битного чтения на x86-64 без префикса LOCK?
- →Что такое проблема ABA и как
std::atomic<std::shared_ptr<T>>помогает?
Оглавление
Базовая реализация std::atomic
Версия 1: на основе мьютекса (portable, дидактическая)
#include <mutex>
#include <utility>
template<typename T>
class Atomic {
public:
Atomic() = default;
explicit Atomic(T val) : val_(std::move(val)) {}
Atomic(const Atomic&) = delete;
Atomic& operator=(const Atomic&) = delete;
T load() const {
std::lock_guard lock(mu_);
return val_;
}
void store(T desired) {
std::lock_guard lock(mu_);
val_ = std::move(desired);
}
// Возвращает true при успехе; при неудаче записывает текущее значение в expected
bool compare_exchange_strong(T& expected, T desired) {
std::lock_guard lock(mu_);
if (val_ == expected) {
val_ = std::move(desired);
return true;
}
expected = val_;
return false;
}
T exchange(T desired) {
std::lock_guard lock(mu_);
T old = std::move(val_);
val_ = std::move(desired);
return old;
}
// Удобный оператор для загрузки
operator T() const { return load(); }
private:
mutable std::mutex mu_;
T val_{};
};
Версия 2: lock-free для trivially_copyable T (gcc/clang)
#include <type_traits>
template<typename T>
class LockFreeAtomic {
static_assert(std::is_trivially_copyable_v<T>);
static_assert(sizeof(T) <= 8); // 8 байт = гарантированно lock-free на x86-64
public:
explicit LockFreeAtomic(T val = {}) : val_(val) {}
T load(int order = __ATOMIC_SEQ_CST) const {
T result;
__atomic_load(&val_, &result, order);
return result;
}
void store(T desired, int order = __ATOMIC_SEQ_CST) {
__atomic_store(&val_, &desired, order);
}
bool compare_exchange_strong(T& expected, T desired,
int success = __ATOMIC_SEQ_CST,
int failure = __ATOMIC_SEQ_CST) {
return __atomic_compare_exchange(&val_, &expected, &desired,
/*weak=*/false, success, failure);
}
private:
T val_;
};
Тест
#include <cassert>
#include <thread>
#include <vector>
int main() {
// Тест 1: базовые операции
Atomic<int> a(0);
a.store(42);
assert(a.load() == 42);
int expected = 42;
assert(a.compare_exchange_strong(expected, 100));
assert(a.load() == 100);
expected = 0; // неверное ожидаемое
assert(!a.compare_exchange_strong(expected, 999));
assert(expected == 100); // обновлено текущим значением
// Тест 2: конкурентный инкремент
Atomic<int> counter(0);
const int N = 1000;
std::vector<std::thread> threads;
for (int i = 0; i < N; ++i) {
threads.emplace_back([&counter] {
int old = counter.load();
while (!counter.compare_exchange_strong(old, old + 1))
; // CAS-цикл
});
}
for (auto& t : threads) t.join();
assert(counter.load() == N);
}
Сравнение подходов
| Характеристика | Мьютекс | __atomic_* builtin | std::atomic<T> |
|---|---|---|---|
| Портабельность | Любая платформа | GCC/Clang | Стандартный C++11 |
| Lock-free | Нет | Да (если is_lock_free()) | Да (для малых типов) |
| Разделяемая память | Нет | Да | Зависит от impl |
| Накладные расходы | Высокие | Минимальные | Минимальные |
Оглавление