← Назад к вопросам
Что такое race condition и как его избежать?
1.3 Junior🔥 211 комментариев
#Linux и операционные системы#Многопоточность и синхронизация#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Race Condition и способы его избежания
Race condition (условие гонки) — это ошибка синхронизации, когда результат программы зависит от временного порядка выполнения потоков. Несколько потоков одновременно обращаются к общему ресурсу, и их операции перемешиваются, приводя к непредсказуемому результату. Это один из самых опасных багов в многопоточных программах, так как он недетерминирован и сложно воспроизводится.
Пример Race Condition
#include <thread>
#include <iostream>
int counter = 0; // Общий ресурс
void increment() {
// counter++ состоит из 3 операций
for (int i = 0; i < 100000; i++) {
counter++; // Непотокобезопасно!
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
// Ожидаем: 200000, На деле: произвольное число!
std::cout << "Counter: " << counter << std::endl;
return 0;
}
1. Решение: Mutex (Взаимное исключение)
#include <thread>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 100000; i++) {
std::lock_guard<std::mutex> lock(mtx);
counter++; // Теперь потокобезопасно
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl; // Гарантированно 200000
return 0;
}
2. Решение: std::atomic (Атомарные операции)
#include <thread>
#include <atomic>
std::atomic<int> counter(0); // Атомарная переменная
void increment() {
for (int i = 0; i < 100000; i++) {
counter++; // Атомарно! Нет race condition
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl; // 200000
return 0;
}
3. Решение: Read-Write Lock (Shared Mutex)
#include <shared_mutex>
int data = 0;
std::shared_mutex data_mutex;
void reader(int id) {
std::shared_lock<std::shared_mutex> lock(data_mutex);
std::cout << "Read: " << data << std::endl;
}
void writer() {
std::unique_lock<std::shared_mutex> lock(data_mutex);
data++;
}
4. Решение: Thread-Safe классы
class ThreadSafeCounter {
private:
int counter = 0;
mutable std::mutex mtx;
public:
void increment() {
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
int getValue() const {
std::lock_guard<std::mutex> lock(mtx);
return counter;
}
};
Лучшие практики избежания Race Conditions
1. Минимизируй критическую секцию
// Плохо: держим lock долго
std::lock_guard<std::mutex> lock(mtx);
data.process(); // Дорогая операция
// Хорошо: держим lock только необходимо
T localData;
{
std::lock_guard<std::mutex> lock(mtx);
localData = data;
}
localData.process(); // Без lock
2. Избегай deadlock'а
// Неправильно: разные порядки блокировок
void thread1() {
std::lock_guard<std::mutex> l1(m1);
std::lock_guard<std::mutex> l2(m2);
}
void thread2() {
std::lock_guard<std::mutex> l2(m2); // Другой порядок — DEADLOCK!
std::lock_guard<std::mutex> l1(m1);
}
// Правильно: одинаковый порядок или std::lock
std::lock(m1, m2);
3. Используй более высокоуровневые конструкции
// Promise/Future для синхронизации
#include <future>
std::promise<int> prom;
std::thread t([&prom]() {
prom.set_value(42);
});
int result = prom.get_future().get();
4. Thread-sanitizer для обнаружения
g++ -fsanitize=thread -g program.cpp
./a.out # Найдёт race conditions во время выполнения
Таблица синхронизации
| Инструмент | Скорость | Сложность | Когда использовать |
|---|---|---|---|
| std::atomic | Очень быстро | Простая | Счётчики, флаги |
| std::mutex | Быстро | Средняя | Критические секции |
| std::shared_mutex | Быстро | Сложная | Много читателей |
| Lock-free | Очень быстро | Очень сложная | Экстремальная нагрузка |
Заключение
Race condition можно избежать:
- std::atomic — для простых операций (самый быстрый)
- std::mutex — для критических секций
- std::shared_mutex — для reader-writer проблем
- Всегда минимизируй критические секции и соблюдай одинаковый порядок блокировок
- Тестируй с thread-sanitizer для обнаружения скрытых ошибок
- Используй высокоуровневые конструкции (Promise/Future вместо ручной синхронизации)