Многопоточность: какой смысл в большем количестве потоков, чем в ядрах? - PullRequest
111 голосов
/ 27 июня 2010

Я думал, что смысл многоядерного компьютера в том, что он может запускать несколько потоков одновременно.В этом случае, если у вас есть четырехъядерный компьютер, какой смысл иметь более 4 потоков одновременно?Разве они не будут просто красть время друг у друга?

Ответы [ 17 ]

61 голосов
/ 27 июня 2010

Ответ вращается вокруг цели потоков, которая заключается в параллелизме: запускать несколько отдельных строк исполнения одновременно. В «идеальной» системе у вас будет один поток, выполняемый на ядро: без прерываний. На самом деле это не так. Даже если у вас четыре ядра и четыре рабочих потока, ваш процесс и его потоки будут постоянно переключаться на другие процессы и потоки. Если вы работаете с любой современной ОС, у каждого процесса есть хотя бы один поток, а у многих - больше. Все эти процессы выполняются одновременно. Возможно, на вашей машине сейчас работает несколько сотен потоков. Вы никогда не столкнетесь с ситуацией, когда поток будет запущен, не «украдя» у него время. (Ну, вы могли бы, если он работает в режиме реального времени , если вы используете ОС реального времени или, даже в Windows, используете приоритет потоков в реальном времени. Но это редко.)

Исходя из этого, ответ: да, более четырех потоков на истинном четырехъядерном компьютере могут дать вам ситуацию, когда они «крадут время друг у друга», , но только если каждому отдельному потоку нужно 100 % CPU . Если поток не работает на 100% (как поток пользовательского интерфейса может не работать, или поток выполняет небольшую работу или ожидает чего-то другого), тогда другой запланированный поток на самом деле является хорошей ситуацией.

На самом деле все сложнее:

  • Что, если у вас есть пять битов работы, которые все должны быть выполнены одновременно? Более разумно запускать их все сразу, чем запускать четыре из них, а затем запускать пятый позже.

  • Редко для потока действительно требуется 100% CPU. Например, в тот момент, когда он использует дисковый или сетевой ввод-вывод, он может потратить время на ожидание, ничего не делая полезного. Это очень распространенная ситуация.

  • Если у вас есть работа, которую нужно выполнить, одним из распространенных механизмов является использование пула потоков. Может показаться, что имеет смысл иметь такое же количество потоков, что и у ядер, но . В пуле потоков .Net доступно до 250 потоков на процессор . Я не уверен, почему они это делают, но я думаю, что это связано с размером задач, которые даны для выполнения в потоках.

Итак: кража времени не является плохой вещью (и на самом деле это не кража: это то, как система должна работать.) Пишите свои многопоточные программы, основываясь на том типе работы, который будут выполнять потоки, что не быть привязанным к процессору. Выясните, сколько потоков вам нужно на основе профилирования и измерения. Может оказаться более полезным думать с точки зрения задач или заданий, а не потоков: писать объекты работы и передавать их в пул для запуска. Наконец, если ваша программа действительно не критична к производительности, не беспокойтесь слишком сильно:)

49 голосов
/ 27 июня 2010

То, что поток существует, не всегда означает, что он активно работает.Во многих приложениях потоков некоторые потоки переходят в спящий режим до тех пор, пока им не придет время что-либо предпринять, например, пользовательский поток запускает потоки, чтобы проснуться, выполнить некоторую обработку и вернуться в спящий режим.Потоки - это отдельные задачи, которые могут работать независимо друг от друга, при этом не нужно знать о ходе выполнения другой задачи.Вполне возможно иметь больше из них, чем у вас есть возможность бежать одновременно;они по-прежнему полезны для удобства, даже если им иногда приходится стоять в очереди друг за другом.

24 голосов
/ 27 июня 2010

Дело в том, что, несмотря на отсутствие реального ускорения, когда число потоков превышает число ядер, вы можете использовать потоки, чтобы отделить части логики, которые не должны быть взаимозависимыми.

Даже в довольно сложном приложении, используя один поток, попытайтесь сделать все быстро, чтобы хэш "потока" вашего кода. Единственный поток тратит большую часть своего времени, опрашивая это, проверяя это, условно вызывая подпрограммы по мере необходимости, и становится трудно видеть что-либо, кроме мелочей.

Сравните это со случаем, когда вы можете выделять потоки задачам, чтобы, глядя на любой отдельный поток, вы могли видеть, что этот поток делает. Например, один поток может заблокировать ожидание ввода от сокета, проанализировать поток в сообщениях, отфильтровать сообщения, а когда появится действительное сообщение, передать его другому рабочему потоку. Рабочий поток может работать на входах из ряда других источников. Код для каждого из них будет показывать чистый, целенаправленный поток, без необходимости делать явные проверки, что больше ничего не нужно делать.

Разделение работы таким образом позволяет вашему приложению полагаться на операционную систему, чтобы планировать, что делать дальше с процессором, так что вам не нужно делать явные условные проверки во всем приложении о том, что может блокировать и что готово процесс.

10 голосов
/ 12 июля 2014

Если поток ожидает ресурс (например, загрузка значения из ОЗУ в регистр, дисковый ввод / вывод, доступ к сети, запуск нового процесса, запрос базы данных или ожидание ввода пользователя), процессор можетработать в другом потоке и вернуться к первому потоку, как только ресурс станет доступен.Это сокращает время, которое процессор проводит в режиме ожидания, поскольку процессор может выполнять миллионы операций вместо простоя.

Рассмотрим поток, который должен считывать данные с жесткого диска.В 2014 году типичное процессорное ядро ​​работает на частоте 2,5 ГГц и может выполнять 4 инструкции за такт.При времени цикла 0,4 нс процессор может выполнять 10 команд в наносекунду.Поскольку типичное время поиска механического жесткого диска составляет около 10 миллисекунд, процессор способен выполнить 100 миллионов инструкций за время, необходимое для считывания значения с жесткого диска.Может быть значительное улучшение производительности с жесткими дисками с небольшим кешем (4 МБ буфера) и гибридными дисками с несколькими ГБ хранилища, поскольку задержка данных при последовательном чтении или чтении из гибридного раздела может быть на несколько порядков быстрее.

Ядро процессора может переключаться между потоками (стоимость приостановки и возобновления потока составляет около 100 тактовых циклов), в то время как первый поток ожидает вход с высокой задержкой (что-нибудь более дорогое, чем регистры (1 такт) и ОЗУ (5 наносекунд))) К ним относятся дисковый ввод-вывод, доступ к сети (задержка 250 мс), чтение данных с компакт-диска или медленной шины или вызов базы данных.Наличие большего количества потоков, чем ядер, означает, что полезная работа может быть выполнена, пока решаются задачи с высокой задержкой.

ЦП имеет планировщик потока, который назначает приоритет каждому потоку и позволяет потоку переходить в спящий режим, а затем возобновлять работу после предварительно определенноговремя.Задача планировщика потока состоит в том, чтобы уменьшить перегрузку, которая произошла бы, если бы каждый поток выполнил всего 100 инструкций перед тем, как снова перевести его в спящий режим.Затраты на переключение потоков могут снизить общую полезную пропускную способность ядра процессора.

По этой причине вы можете разбить проблему на разумное количество потоков.Если вы пишете код для выполнения умножения матриц, создание одного потока на ячейку в выходной матрице может быть чрезмерным, тогда как один поток на строку или на n строк в выходной матрице может снизить накладные расходы на создание,приостановка и возобновление потоков.

Вот почему так важно предсказание ветвлений.Если у вас есть оператор if, который требует загрузки значения из ОЗУ, но в теле операторов if и else используются значения, уже загруженные в регистры, процессор может выполнить одну или обе ветви до того, как условие будет оценено.Как только условие возвращается, процессор применяет результат соответствующей ветви и отбрасывает другую.Выполнение потенциально бесполезной работы здесь, вероятно, лучше, чем переключение на другой поток, что может привести к перебоям.

Поскольку мы перешли от одноядерных процессоров с высокой тактовой частотой к многоядерным процессорам, разработка чиповосновное внимание уделялось созданию большего количества ядер на кристалл, улучшению совместного использования ресурсов внутри кристалла, улучшенным алгоритмам прогнозирования ветвлений, улучшению издержек на переключение потоков и лучшему планированию потоков.

6 голосов
/ 27 июня 2010

Я категорически не согласен с утверждением @ kyoryu о том, что идеальным числом является один поток на процессор.

Подумайте об этом так: почему у нас многозадачные операционные системы?На протяжении большей части истории компьютеров почти все компьютеры имели один процессор.Однако с 1960-х годов все «настоящие» компьютеры имели многозадачные (или многозадачные) операционные системы.

Вы запускаете несколько программ, так что одну можно запускать, в то время как другие блокируются для таких вещей, как IO.

позволяет отложить в сторону аргументы о том, были ли версии Windows до NT многозадачными.С тех пор каждая настоящая ОС имела многозадачность.Некоторые не предоставляют его пользователям, но в любом случае делают это, например, слушая радио на мобильном телефоне, разговаривая с чипом GPS, принимая данные от мыши и т. Д.

Потоки - это просто задачи, которые немного большеэффективный.Между задачей, процессом и потоком нет принципиальной разницы.

Процессор - это ужасная вещь, которую тратить впустую, поэтому подготовьте ее к использованию, когда сможете.

Я будусогласитесь, что с большинством процедурных языков, C, C ++, Java и т. д., написание правильного поточно-безопасного кода - большая работа.Сегодня, когда на рынке есть 6-ядерные процессоры и 16-ядерные ЦП, я ожидаю, что люди отойдут от этих старых языков, так как многопоточность становится все более важным требованием.

Несогласие с@kyoryu это просто ИМХО, остальное факт.

5 голосов
/ 27 июня 2010

Хотя вы, безусловно, можете использовать потоки для ускорения вычислений в зависимости от вашего оборудования, одним из их основных применений является одновременное выполнение нескольких задач по причинам удобства использования.

Например, если выприходится выполнять некоторую обработку в фоновом режиме, а также реагировать на ввод пользовательского интерфейса, вы можете использовать потоки.Без потоков пользовательский интерфейс зависал бы каждый раз, когда вы пытались выполнить какую-либо тяжелую обработку.

Также смотрите этот связанный вопрос: Практическое использование для потоков

5 голосов
/ 27 июня 2010

Большинство ответов выше говорят о производительности и одновременной работе.Я собираюсь подойти к этому под другим углом.

Давайте рассмотрим, скажем, упрощенную программу эмуляции терминала.Вы должны сделать следующее:

  • следить за входящими символами из удаленной системы и отображать их
  • следить за вещами, поступающими с клавиатуры, и отправлять их в удаленную систему

(Реальные эмуляторы терминала делают больше, в том числе потенциально отображают то, что вы печатаете на дисплее, но сейчас мы пропустим это.)

Теперь цикл для чтения изПульт ДУ прост, согласно следующему псевдокоду:

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

Логика, даже в этом намеренно упрощенном примере, который не учитывает сложность обмена данными в реальном мире, заключается вдовольно запутанный.Однако при многопоточности даже на одном ядре две петли псевдокода могут существовать независимо друг от друга, не чередуя свою логику.Поскольку оба потока будут в основном связаны с вводом-выводом, они не будут сильно нагружать ЦП, даже если они, строго говоря, потребляют больше ресурсов ЦП, чем интегрированный цикл.

Теперь, конечно, использование в реальных условиях сложнее, чем выше.Но сложность интегрированного цикла возрастает по экспоненте, поскольку вы добавляете больше проблем в приложение.Логика становится все более фрагментированной, и вы должны начать использовать методы, такие как конечные автоматы, сопрограммы и т. Д., Чтобы сделать вещи управляемыми.Управляемый, но не читаемый.Многопоточность делает код более читабельным.

Так почему бы вам не использовать многопоточность?

Что ж, если ваши задачи связаны с ЦП, а не с вводом / выводом, многопоточность фактически замедляет работу вашей системы.,Производительность пострадает.Много, во многих случаях.(«Перебрасывание» является распространенной проблемой, если вы отбрасываете слишком много потоков, связанных с процессором. В результате вы тратите больше времени на изменение активных потоков, чем на выполнение содержимого самих потоков.) Кроме того, одна из причин, по которой логика вышетак просто, что я очень сознательно выбрал упрощенный (и нереальный) пример.Если вы хотите отобразить то, что было напечатано на экране, тогда вы получаете новый мир боли, когда вводите блокировку общих ресурсов.С одним общим ресурсом это не такая большая проблема, но она начинает становиться все больше и больше, поскольку у вас появляется больше ресурсов для совместного использования.

Итак, в конце концов, многопоточность - это многое.Например, речь идет о том, чтобы сделать связанные с вводом / выводом процессы более отзывчивыми (даже если они менее эффективными в целом), как уже говорили некоторые.Это также облегчает выполнение логики (но только если вы минимизируете общее состояние).Речь идет о многих вещах, и вы должны решить, перевешивают ли его преимущества его недостатки в каждом конкретном случае.

5 голосов
/ 27 июня 2010

Представьте себе веб-сервер, который должен обслуживать произвольное количество запросов.Вы должны обслуживать запросы параллельно, потому что в противном случае каждый новый запрос должен ждать, пока все другие запросы не будут выполнены (включая отправку ответа через Интернет).В этом случае большинство веб-серверов имеют меньше ядер, чем число запросов, которые они обычно обслуживают.

Это также облегчает разработчику сервера: вам нужно только написать потоковую программу, которая обслуживает запросвам не нужно думать о хранении нескольких запросов, порядке их обработки и т. д.

3 голосов
/ 27 июня 2010

Многие потоки будут спать, ожидая ввода пользователя, ввода-вывода и других событий.

2 голосов
/ 27 июня 2010

Потоки могут помочь с отзывчивостью в приложениях пользовательского интерфейса. Кроме того, вы можете использовать потоки, чтобы получить больше работы от ваших ядер. Например, на одном ядре у вас может быть один поток, выполняющий ввод-вывод, а другой - некоторые вычисления. Если бы он был однопоточным, ядро ​​могло бы фактически простаивать в ожидании завершения ввода-вывода. Это пример довольно высокого уровня, но потоки, безусловно, могут быть использованы для увеличения вашего процессора немного сильнее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...