Почему нельзя все сделать на потоках
Что проверяют
Знаете ли вы цену потока операционной системы и понимаете ли, что ускорение ограничено долей последовательной работы. Хотят услышать инженерный расчёт, а не список страшилок про гонки.
Как отвечать
Начните с цены. Поток это отдельный стек, обычно мегабайты виртуальной памяти, запись в планировщике и переключение контекста через ядро с потерей содержимого кэшей. Тысяча потоков, висящих на сокетах, это тысяча стеков и постоянная работа планировщика вхолостую, ту же задачу решает один цикл событий или пул размером с число ядер.
Дальше про пределы ускорения. Доля последовательного кода ограничивает выигрыш, и после некоторого числа потоков синхронизация съедает больше, чем даёт параллельность. Отдельно назовите память как узкое место: параллельный проход по большому массиву упирается в пропускную способность, а два потока, пишущие в соседние поля одной строки кэша, работают медленнее одного.
Корректность добавьте третьим пунктом, но без общих слов: гонка данных в C++ это неопределённое поведение, то есть компилятор оптимизирует код исходя из того, что гонки нет. Инструмент здесь -fsanitize=thread, а не вычитывание глазами, а несколько мьютексов захватываются через std::scoped_lock и в едином порядке.
Итог формулируйте так: потоки нужны, чтобы занять ядра вычислениями. Для ожидания ввода и вывода нужен асинхронный ввод-вывод, для потока мелких задач пул, а отдельный std::thread на каждую задачу это почти всегда ошибка.
Где спотыкаются
Перечисляют гонки и взаимные блокировки и останавливаются. Это ответ на вопрос, чем опасны потоки, а спрашивали, почему на них нельзя сделать всё: без цены потока и предела ускорения ответа нет.
Говорят, что гонка даст неверное значение переменной. В C++ она даёт неопределённое поведение программы целиком, и сломаться может совсем не то место, за которым наблюдали. volatile при этом не средство синхронизации, он про доступ к памяти устройства.
Создают поток на каждую задачу и забывают про завершение: объект потока, который не присоединили и не отсоединили, в деструкторе вызывает std::terminate. Отсюда std::jthread или своя обёртка, а не обещание не забыть.