Вопрос проверяет понимание потокобезопасных операций с примитивами и необходимости использования атомарных типов для предотвращения состояния гонки в многопоточных программах.
В многопоточном программировании одновременный доступ к общим данным из нескольких потоков может привести к неопределённому поведению. Обычные примитивные типы данных (например, целое число int) не гарантируют атомарность операций чтения-модификации-записи на уровне процессора.
Рассмотрим операцию инкремента counter++. На уровне машинных инструкций она распадается на три шага: чтение значения из памяти в регистр, увеличение регистра, запись результата обратно в память. Если два потока выполняют эту операцию одновременно, они могут прочитать одно и то же старое значение, увеличить его и записать, в результате чего один инкремент будет потерян.
// Пример на C++ (небезопасный)
int counter = 0;
// Поток 1: counter++ (читает 0, увеличивает до 1, пишет 1)
// Поток 2: counter++ (может прочитать 0 до того, как Поток 1 запишет 1)
// Итог: counter может стать 1 вместо 2.Атомарные типы (например, std::atomic<int> в C++ или AtomicInteger в Java) гарантируют, что такие операции выполняются как единое целое. Процессор или библиотека времени выполнения используют специальные инструкции (например, CAS – Compare-And-Swap) или барьеры памяти, чтобы обеспечить изоляцию.
// Пример на C++ с использованием std::atomic
#include <atomic>
#include <thread>
std::atomic<int> atomic_counter{0};
void increment() {
for (int i = 0; i < 1000; ++i) {
atomic_counter++; // Атомарная операция
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
// Гарантированно получим 2000
return 0;
}Вывод: Атомарные типы следует применять, когда необходимо обеспечить потокобезопасность для простых операций над общими примитивами (инкремент, сравнение с обменом) без накладных расходов на блокировки. Они являются фундаментальным строительным блоком для эффективного многопоточного кода.