У меня вопрос, почему программисты любят параллельные и многопоточные программы вообще.
Я рассматриваю 2 основных подхода:
- асинхронный подход, основанный в основном на сигналах, или просто асинхронный подход, как его называют многие документы и языки, например, новый C # 5.0, и «сопутствующий поток», который управляет политикой вашего конвейера.
- параллельный подход или многопоточный подход
Я просто скажу, что я думаю об аппаратном обеспечении здесь и наихудшем сценарии, и я сам проверил эти 2 парадигмы, асинхронная парадигма является победителем в том смысле, что я не понимаю, почему люди 90% времени говорить о многопоточности, когда они хотят ускорить работу или эффективно использовать свои ресурсы.
Я протестировал многопоточные программы и асинхронные программы на старой машине с четырехъядерным процессором Intel, который не имеет контроллера памяти внутри ЦП, память полностью управляется материнской платой, и в этом случае производительность ужасна. Многопоточное приложение, даже относительно небольшое количество потоков, таких как 3-4-5, может быть проблемой, приложение не отвечает и просто медленно и неприятно.
С другой стороны, хороший асинхронный подход, вероятно, не быстрее, но и не хуже, мое приложение просто ждет результата и не зависает, оно отзывчиво и происходит гораздо лучшее масштабирование.
Я также обнаружил, что изменение контекста в мире многопоточности не так уж и дешево в сценарии реального мира, на самом деле это довольно дорого, особенно если у вас более двух потоков, которые нужно циклически менять и менять местами друг для друга для вычисления.
На современных процессорах ситуация ничем не отличается, контроллер памяти интегрирован, но я хочу сказать, что процессоры x86 - это, по сути, последовательный компьютер, а контроллер памяти работает так же, как на старой машине с контроллером внешней памяти на материнской плате. , Переключение контекста по-прежнему актуально для моего приложения, и тот факт, что контроллер памяти интегрирован или что новый процессор имеет более 2 ядер, не является для меня выгодной сделкой.
Из того, что я испытал, параллельный подход хорош в теории, но не так хорош на практике, с моделью памяти, навязанной аппаратными средствами, трудно эффективно использовать эту парадигму, также он создает много проблем, начиная от использования моих структур данных для объединения нескольких потоков.
Кроме того, обе парадигмы не предлагают какой-либо опоры безопасности, когда задача или работа будет выполнена в определенный момент времени, что делает их действительно похожими с функциональной точки зрения.
Согласно модели памяти X86, почему большинство людей предлагают использовать параллелизм с C ++, а не просто асинхронный подход? Кроме того, почему бы не рассмотреть наихудший сценарий компьютера, где переключение контекста, вероятно, дороже, чем само вычисление?
источник
Ответы:
У вас есть несколько ядер / процессоров, используйте их
Асинхронный является лучшим для этого тяжелого IO , связанных с переработкой , но как насчет тяжелой работы процессора обработки?
Проблема возникает, когда однопоточный код блокируется (то есть застревает) в длительном процессе. Например, помните, когда при печати документа текстового процессора все приложение зависало до отправки задания? Замораживание приложений является побочным эффектом однопоточной блокировки приложений во время задачи, интенсивно использующей процессор.
В многопоточном приложении задачи с интенсивным использованием ЦП (например, задание на печать) можно отправлять в фоновый рабочий поток, освобождая тем самым поток пользовательского интерфейса.
Аналогично, в многопроцессорном приложении задание может быть отправлено через обмен сообщениями (например, IPC, сокеты и т. Д.) В подпроцесс, специально предназначенный для обработки заданий.
На практике асинхронный и многопоточный / процессный код имеют свои преимущества и недостатки.
Вы можете увидеть тенденцию в основных облачных платформах, поскольку они будут предлагать экземпляры, специализированные для обработки с привязкой к процессору, и экземпляры, специализированные для обработки с привязкой к вводу-выводу.
Примеры:
Чтобы поместить это в перспективу ...
Веб-сервер является прекрасным примером платформы, которая тесно связана с вводом-выводом. Многопоточный веб-сервер, который назначает один поток для каждого соединения, плохо масштабируется, поскольку каждый поток несет больше накладных расходов из-за увеличения объема переключения контекста и блокировки потоков на общих ресурсах. В то время как асинхронный веб-сервер будет использовать одно адресное пространство.
Аналогично, приложение, специализирующееся на кодировании видео, будет работать намного лучше в многопоточной среде, потому что тяжелая обработка будет блокировать основной поток, пока работа не будет завершена. Существуют способы смягчить это, но гораздо проще иметь один поток, управляющий очередью, второй поток, управляющий очисткой, и пул потоков, управляющих тяжелой обработкой. Связь между потоками происходит только тогда, когда задачи назначены / выполнены, поэтому накладные расходы на блокировку потоков сведены к минимуму.
Лучшее приложение часто использует комбинацию обоих. Например, веб-приложение может использовать nginx (то есть асинхронный однопоточный) в качестве балансировщика нагрузки для управления потоком входящих запросов, аналогичный асинхронный веб-сервер (например, Node.js) для обработки http-запросов и набор многопоточных серверов. обрабатывать загрузку / потоковую передачу / кодирование контента и т. д.
За эти годы было много религиозных войн между многопоточными, многопроцессными и асинхронными моделями. Как и в большинстве случаев, лучшим ответом на самом деле должен быть «это зависит».
Он придерживается той же концепции, которая оправдывает параллельное использование архитектур GPU и CPU. Две специализированные системы, работающие совместно, могут иметь гораздо большее улучшение, чем один монолитный подход.
Ни один из них не лучше, потому что оба имеют свое применение. Используйте лучший инструмент для работы.
Обновить:
Я удалил ссылку на Apache и внес небольшие исправления. Apache использует многопроцессорную модель, которая разветвляет процесс для каждого запроса, увеличивая количество переключений контекста на уровне ядра. Кроме того, поскольку память не может быть разделена между процессами, каждый запрос влечет за собой дополнительную стоимость памяти.
Многопоточность обходится, требуя дополнительной памяти, потому что она опирается на общую память между потоками. Совместно используемая память устраняет дополнительные накладные расходы памяти, но по-прежнему влечет за собой штраф за повышенное переключение контекста. Кроме того, чтобы не допустить возникновения условий гонки, требуются блокировки потоков (которые гарантируют исключительный доступ только к одному потоку за раз) для любых ресурсов, которые совместно используются потоками.
Забавно, что вы говорите: «Программисты, кажется, любят параллельные и многопоточные программы в целом». Многопоточное программирование универсально страшно для любого, кто сделал сколько-нибудь значительную часть этого в свое время. Мертвые блокировки (ошибка, которая возникает, когда ресурс по ошибке заблокирован двумя разными источниками, блокирующими как-либо окончание), так и условия гонки (когда программа ошибочно выдает неправильный результат случайным образом из-за неправильной последовательности) являются одними из самых сложных для отслеживания вниз и исправить.
Update2:
Вопреки общему утверждению о том, что IPC быстрее, чем сетевые (т.е. сокетные) соединения. Это не всегда так . Имейте в виду, что это обобщения, и детали реализации могут оказать огромное влияние на результат.
источник
Асинхронный подход Microsoft является хорошей заменой для наиболее распространенных целей многопоточного программирования: повышение скорости реагирования на задачи ввода-вывода.
Тем не менее, важно понимать, что асинхронный подход вообще не способен ни повысить производительность, ни улучшить скорость реагирования на задачи с интенсивным использованием ЦП.
Многопоточность для отзывчивости
Многопоточность для отзывчивости - это традиционный способ обеспечить отзывчивость программы во время тяжелых задач ввода-вывода или сложных вычислительных задач. Вы сохраняете файлы в фоновом потоке, чтобы пользователь мог продолжить свою работу, не дожидаясь завершения работы жесткого диска. Поток ввода-вывода часто блокирует ожидание завершения части записи, поэтому переключение контекста происходит часто.
Аналогично, при выполнении сложных вычислений вы хотите разрешить регулярное переключение контекста, чтобы пользовательский интерфейс оставался отзывчивым, и пользователь не думал, что программа потерпела крах.
В целом цель здесь не в том, чтобы несколько потоков работали на разных процессорах. Вместо этого мы просто заинтересованы в том, чтобы переключение контекста происходило между длительной фоновой задачей и пользовательским интерфейсом, чтобы пользовательский интерфейс мог обновлять и отвечать пользователю во время выполнения фоновой задачи. В общем случае пользовательский интерфейс не потребляет много ресурсов процессора, а многопоточная среда или ОС обычно решают запустить их на одном и том же процессоре.
Мы фактически теряем общую производительность из-за дополнительных затрат на переключение контекста, но нам все равно, потому что производительность процессора не была нашей целью. Мы знаем, что обычно у нас больше ресурсов процессора, чем нам нужно, и поэтому наша цель в отношении многопоточности состоит в том, чтобы выполнить задачу для пользователя, не тратя время пользователя.
«Асинхронная» альтернатива
«Асинхронный подход» меняет эту картину, позволяя переключать контексты в одном потоке. Это гарантирует, что все наши задачи будут выполняться на одном процессоре, и может обеспечить некоторые незначительные улучшения производительности с точки зрения меньшего количества создания / очистки потоков и меньшего количества реальных переключений контекста между потоками.
Вместо создания нового потока, ожидающего получения сетевого ресурса (например, загрузки изображения), используется
async
метод, при которомawait
изображение становится доступным и, тем временем, уступает вызывающему методу.Основным преимуществом здесь является то, что вам не нужно беспокоиться о проблемах с многопоточностью, таких как избежание взаимоблокировки, так как вы вообще не используете блокировки и синхронизацию, и программисту нужно немного настроить фоновый поток и вернуться назад. в потоке пользовательского интерфейса, когда возвращается результат для безопасного обновления пользовательского интерфейса.
Я не слишком углублялся в технические детали, но у меня сложилось впечатление, что управление загрузкой с периодической легкой загрузкой ЦП становится задачей не для отдельного потока, а скорее чем-то более похожим на задачу в очереди событий пользовательского интерфейса, и когда загрузка завершена, асинхронный метод возобновляется из этой очереди событий. Другими словами,
await
означает что-то вроде «проверить, доступен ли мне нужный результат, если нет, вернуть меня в очередь задач этого потока».Обратите внимание, что этот подход не решит проблему задачи, интенсивно использующей процессор: данных ждать не приходится, поэтому мы не можем получить переключения контекста, которые нам нужны, без создания фактического фонового рабочего потока. Конечно, все еще может быть удобно использовать асинхронный метод для запуска фонового потока и возврата результата в программе, которая широко использует асинхронный подход.
Многопоточность для производительности
Поскольку вы говорите о «производительности», я также хотел бы обсудить, как многопоточность может использоваться для повышения производительности, что совершенно невозможно при однопоточном асинхронном подходе.
Когда вы на самом деле находитесь в ситуации, когда вам не хватает мощности ЦП на одном ЦП, и вы хотите использовать многопоточность для повышения производительности, на самом деле это часто бывает трудно сделать. С другой стороны, если одному процессору не хватает вычислительной мощности, это также часто единственное решение, которое может позволить вашей программе делать то, что вы хотели бы выполнить в разумные сроки, что и делает работу стоящей.
Тривиальный параллелизм
Конечно, иногда может быть легко получить реальное ускорение от многопоточности.
Если у вас есть большое количество независимых задач, требующих большого объема вычислений (то есть задач, чьи входные и выходные данные очень малы по сравнению с вычислениями, которые необходимо выполнить для определения результата), то вы часто можете получить значительное ускорение за счет создание пула потоков (с соответствующим размером в зависимости от количества доступных процессоров) и наличие главного потока для распределения работы и сбора результатов.
Практическая многопоточность для производительности
Я не хочу выдвигать себя в качестве эксперта, но у меня сложилось впечатление, что, как правило, наиболее практичная многопоточность для производительности, которая происходит в наши дни, - это поиск мест в приложении с тривиальным параллелизмом и использование нескольких потоков. пожинать плоды.
Как и в случае любой оптимизации, обычно лучше оптимизировать после того, как вы профилировали производительность вашей программы и определили «горячие точки»: программу легко замедлить, произвольно решив, что эта часть должна выполняться в одном потоке, а другая - в другом, без сначала определить, занимают ли обе части значительную часть процессорного времени.
Дополнительный поток означает больше затрат на установку / разборку и либо больше переключений контекста, либо больше затрат на связь между процессорами. Если он не выполняет достаточно работы, чтобы компенсировать эти затраты, если он находится на отдельном процессоре, и не нуждается в отдельном потоке по соображениям отзывчивости, он замедлит работу без какой-либо выгоды.
Ищите задачи, которые имеют мало взаимозависимостей и занимают значительную часть времени выполнения вашей программы.
Если у них нет взаимозависимостей, то это случай тривиального параллелизма, вы можете легко настроить каждый из них с потоком и наслаждаться преимуществами.
Если вы можете найти задачи с ограниченной взаимозависимостью, так что блокировка и синхронизация для обмена информацией не сильно их замедляют, тогда многопоточность может дать некоторое ускорение, при условии, что вы будете осторожны, чтобы избежать опасностей тупика из-за неисправной логики при синхронизации или неверные результаты из-за отсутствия синхронизации, когда это необходимо.
В качестве альтернативы, некоторые из наиболее распространенных приложений для многопоточности не (в некотором смысле) не ищут ускорения заранее определенного алгоритма, но вместо этого для большего бюджета для алгоритма, который они планируют написать: если вы пишете игровой движок и ваш ИИ должен принимать решение в пределах вашей частоты кадров, вы часто можете дать своему ИИ больший бюджет цикла ЦП, если вы можете назначить ему свой собственный ЦП.
Однако обязательно профилируйте потоки и убедитесь, что они выполняют достаточно работы, чтобы в какой-то момент компенсировать затраты.
Параллельные алгоритмы
Есть также много проблем, которые могут быть ускорены при использовании нескольких процессоров, но они слишком монолитны, чтобы просто делиться между процессорами.
Параллельные алгоритмы должны быть тщательно проанализированы на предмет их времени выполнения big-O с точки зрения лучшего из доступных непараллельных алгоритмов, поскольку затраты на межпроцессорное взаимодействие очень легко исключают любые выгоды от использования нескольких процессоров. В общем, они должны использовать меньше межпроцессорного взаимодействия (в терминах big-O), чем они используют вычисления для каждого CPU.
На данный момент это все еще в значительной степени пространство для академических исследований, отчасти из-за необходимого сложного анализа, отчасти потому, что тривиальный параллелизм довольно распространен, отчасти потому, что на наших компьютерах еще не так много процессорных ядер, что вызывает проблемы, которые не может быть решена в разумные сроки на одном процессоре может быть решена в разумные сроки с использованием всех наших процессоров.
источник
И есть твоя проблема. Адаптивный пользовательский интерфейс не создает приложения. Часто наоборот. Большая часть времени тратится на проверку ввода пользовательского интерфейса, а не на выполнение рабочих потоков.
Что касается «просто» асинхронного подхода, то это также многопоточность, хотя в большинстве сред она настроена для этого конкретного случая использования . В других эта асинхронность выполняется через сопрограммы, которые ... не всегда параллельны.
Честно говоря, я считаю, что асинхронные операции труднее рассуждать и использовать таким способом, который действительно обеспечивает преимущество (производительность, надежность, ремонтопригодность) даже по сравнению с ... более ручными подходами.
источник