Вопрос проверяет понимание проблемы состояния гонки при работе с общими ресурсами в многопоточном программировании и способы её решения.
Проблема гонки потоков (race condition) — классическая проблема многопоточного программирования, возникающая, когда несколько потоков одновременно обращаются к общему ресурсу (например, переменной-счётчику) и хотя бы один из потоков выполняет запись. Без синхронизации операции чтения и записи могут "перекрываться", приводя к потере данных и недетерминированному результату.
Рассмотрим простой счётчик. Операция инкремента counter++ на уровне процессора обычно состоит из трёх шагов: чтение значения из памяти, увеличение его на единицу, запись результата обратно. Если два потока выполняют эту операцию одновременно, они могут прочитать одно и то же исходное значение, независимо увеличить его и записать одинаковый результат, что приведёт к потере одного из инкрементов.
std::atomic в C++ или AtomicInteger в Java), которые гарантируют, что операции над ними выполняются как единое целое, без вмешательства других потоков.synchronized для методов или блоков кода.Демонстрация проблемы и её решения с помощью мьютекса и атомарного типа.
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <atomic>
// Глобальный счётчик с проблемой гонки
int unsafe_counter = 0;
// Счётчик, защищённый мьютексом
int mutex_counter = 0;
std::mutex mtx;
// Атомарный счётчик
std::atomic<int> atomic_counter(0);
void increment_unsafe() {
for (int i = 0; i < 10000; ++i) {
unsafe_counter++; // Потенциальная гонка!
}
}
void increment_with_mutex() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
mutex_counter++;
}
}
void increment_atomic() {
for (int i = 0; i < 10000; ++i) {
atomic_counter++; // Атомарная операция
}
}
int main() {
std::vector<std::thread> threads;
// Запуск потоков для каждого метода
// ... (код создания и запуска потоков)
// После join выведем значения
std::cout << "Unsafe: " << unsafe_counter << std::endl;
std::cout << "Mutex: " << mutex_counter << std::endl;
std::cout << "Atomic: " << atomic_counter << std::endl;
return 0;
}В этом примере unsafe_counter почти наверняка покажет значение меньше ожидаемого (например, 20000 при двух потоках), в то время как mutex_counter и atomic_counter будут корректными.
Синхронизация доступа к общим данным необходима в любом многопоточном приложении: веб-серверы, обрабатывающие параллельные запросы; системы реального времени; игровые серверы; приложения для анализа данных, использующие многопоточность для ускорения вычислений.
Вывод: Для исправления гонки в счётчике используйте атомарные операции для простых числовых типов, так как они обычно эффективнее мьютексов. Мьютексы же предпочтительнее для защиты более сложных структур данных или когда критическая секция содержит несколько операций, которые должны выполняться атомарно вместе.