Большинство ответов выше говорят о производительности и одновременной работе. Я собираюсь подойти к этому с другой стороны.
Возьмем, к примеру, упрощенную программу эмуляции терминала. Вам необходимо сделать следующее:
- следить за входящими символами из удаленной системы и отображать их
- следить за вещами, исходящими с клавиатуры, и отправлять их в удаленную систему
(Настоящие эмуляторы терминала делают больше, в том числе могут отображать на дисплее то, что вы вводите, но мы пока об этом не говорим.)
Теперь цикл для чтения с пульта прост, согласно следующему псевдокоду:
while get-character-from-remote:
print-to-screen character
Цикл для мониторинга клавиатуры и отправки также прост:
while get-character-from-keyboard:
send-to-remote character
Но проблема в том, что вы должны делать это одновременно. Теперь код должен выглядеть примерно так, если у вас нет потоковой передачи:
loop:
check-for-remote-character
if remote-character-is-ready:
print-to-screen character
check-for-keyboard-entry
if keyboard-is-ready:
send-to-remote character
Логика, даже в этом намеренно упрощенном примере, который не принимает во внимание реальную сложность коммуникаций, довольно запутана. Однако при распараллеливании, даже на одном ядре, два цикла псевдокода могут существовать независимо, не чередуя свою логику. Поскольку оба потока будут в основном связаны с вводом-выводом, они не создают большой нагрузки на ЦП, хотя, строго говоря, они расходуют больше ресурсов ЦП, чем интегрированный цикл.
Конечно, реальное использование намного сложнее, чем описано выше. Но сложность интегрированного цикла возрастает экспоненциально по мере того, как вы добавляете дополнительные проблемы в приложение. Логика становится все более фрагментированной, и вам нужно начать использовать такие методы, как конечные автоматы, сопрограммы и т. Д., Чтобы сделать вещи управляемыми. Управляемый, но не читаемый. Многопоточность делает код более читабельным.
Так почему бы вам не использовать потоки?
Что ж, если ваши задачи связаны с процессором, а не с вводом-выводом, многопоточность фактически замедляет вашу систему. Пострадает производительность. Во многих случаях много. («Перебивание» - распространенная проблема, если вы отбрасываете слишком много потоков, связанных с ЦП. Вы тратите больше времени на изменение активных потоков, чем на выполнение содержимого самих потоков.) Кроме того, одна из причин, по которой приведенная выше логика заключается в так просто, что я сознательно выбрал упрощенный (и нереалистичный) пример. Если вы хотите отобразить то, что было напечатано на экране, вы получите новый мир боли, когда вы введете блокировку общих ресурсов. Имея только один общий ресурс, это не такая уж большая проблема, но она начинает становиться все большей и большей проблемой, поскольку у вас появляется больше ресурсов, которыми можно поделиться.
В конце концов, многопоточность касается многих вещей. Например, речь идет о том, чтобы сделать процессы, связанные с вводом-выводом, более отзывчивыми (даже если они в целом менее эффективны), как некоторые уже говорили. Это также касается упрощения логики (но только если вы минимизируете общее состояние). Речь идет о многих вещах, и вам нужно решить, перевешивают ли его преимущества его недостатки в каждом конкретном случае.