Вопрос проверяет понимание синхронизации потоков и умение реализовать потокобезопасную структуру данных, что критично для предотвращения состояния гонки в многопоточных приложениях.
Потокобезопасный счетчик — это структура данных, значение которой можно изменять (например, инкрементировать) из нескольких потоков одновременно без риска получить некорректный результат из-за состояния гонки (race condition). Состояние гонки возникает, когда два или более потока читают и пишут в одну переменную без синхронизации, и итоговое значение зависит от порядка выполнения инструкций, что ведет к ошибкам.
Существует два основных подхода для обеспечения потокобезопасности счетчика:
Пример на C++ с использованием std::mutex:
#include
#include
#include
#include
class ThreadSafeCounter {
private:
int value = 0;
std::mutex mtx;
public:
void increment() {
std::lock_guard lock(mtx);
++value;
}
int get() {
std::lock_guard lock(mtx);
return value;
}
};
int main() {
ThreadSafeCounter counter;
std::vector threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&counter]() {
for (int j = 0; j < 1000; ++j) {
counter.increment();
}
});
}
for (auto& t : threads) t.join();
std::cout << counter.get() << std::endl; // Всегда 10000
return 0;
}Пример на Java с использованием AtomicInteger:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AtomicCounterExample {
public static void main(String[] args) throws InterruptedException {
AtomicInteger counter = new AtomicInteger(0);
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet(); // Атомарный инкремент
}
});
}
executor.shutdown();
while (!executor.isTerminated()) { }
System.out.println(counter.get()); // Всегда 10000
}
}Потокобезопасные счетчики используются везде, где требуется собирать статистику в многопоточных системах: подсчет количества запросов в веб-сервере, отслеживание числа активных соединений, реализация ограничителей скорости (rate limiters), счетчики ссылок в умных указателях, и в любых структурах данных, где значение разделяется между потоками.
Вывод: Используйте атомарные операции для простых счетчиков, когда важна максимальная производительность и низкие задержки. Мьютексы подойдут для более сложных сценариев, где нужно синхронизировать доступ к нескольким связанным переменным или ресурсам. Выбор зависит от конкретных требований к производительности и сложности логики.