Я думал, что смысл многоядерного компьютера в том, что он может одновременно запускать несколько потоков. В таком случае, если у вас есть четырехъядерный компьютер, какой смысл иметь более 4 потоков, работающих одновременно? Разве они не будут просто красть время (ресурсы ЦП) друг у друга?
multithreading
hardware
cpu-cores
Ник Хайнер
источник
источник
Ответы:
Ответ вращается вокруг цели потоков, то есть параллелизма: запускать несколько отдельных строк выполнения одновременно. В «идеальной» системе у вас будет один поток, выполняющийся на ядро: без прерываний. На самом деле это не так. Даже если у вас четыре ядра и четыре рабочих потока, ваш процесс и его потоки будут постоянно переключаться для других процессов и потоков. Если вы используете любую современную ОС, у каждого процесса есть хотя бы один поток, а у многих - больше. Все эти процессы запущены одновременно. Вероятно, прямо сейчас на вашем компьютере работает несколько сотен потоков. Вы никогда не столкнетесь с ситуацией, когда поток запускается без «украденного» времени. (Ну, вы могли бы, если он работает в режиме реального времени, если вы используете ОС реального времени или даже в Windows, используете приоритет потока в реальном времени. Но бывает редко.)
На этом фоне ответ: да, более четырех потоков на настоящей четырехъядерной машине могут дать вам ситуацию, когда они «крадут время друг у друга», но только если каждому отдельному потоку требуется 100% ЦП . Если поток не работает на 100% (в отличие от потока пользовательского интерфейса, или потока, выполняющего небольшой объем работы или ожидающего чего-то еще), то другой запланированный поток на самом деле является хорошей ситуацией.
На самом деле все сложнее:
Что, если у вас есть пять задач, которые нужно выполнить сразу? Более разумно запускать их все сразу, чем запускать четыре из них, а затем запускать пятый позже.
Редко, когда потоку действительно нужен 100% процессор. Например, в тот момент, когда он использует дисковый или сетевой ввод-вывод, он потенциально может потратить время на ожидание, не делая ничего полезного. Это очень распространенная ситуация.
Если у вас есть работа, которую необходимо выполнить, одним из распространенных механизмов является использование пула потоков. Может показаться, что имеет смысл иметь такое же количество потоков, что и количество ядер, но пул потоков .Net имеет до 250 потоков, доступных на процессор . Я не уверен, почему они это делают, но предполагаю, что это связано с размером задач, которые выполняются в потоках.
Итак: кража времени - неплохая вещь (и на самом деле это не кража: это то, как должна работать система). Пишите свои многопоточные программы в зависимости от того, какую работу будут выполнять потоки, а это может не быть ЦП. -связанный. Определите необходимое количество потоков на основе профилирования и измерений. Возможно, вам будет полезнее думать о задачах или заданиях, а не о потоках: напишите объекты работы и передайте их в пул для выполнения. Наконец, если ваша программа действительно не критична к производительности, не беспокойтесь слишком сильно :)
источник
Тот факт, что поток существует, не всегда означает, что он активно работает. Во многих приложениях потоков некоторые потоки переходят в спящий режим до тех пор, пока им не придет время что-то сделать - например, пользовательский ввод запускает потоки для пробуждения, выполнения некоторой обработки и возврата в спящий режим.
По сути, потоки - это отдельные задачи, которые могут работать независимо друг от друга, без необходимости знать о ходе выполнения другой задачи. Вполне возможно, что их будет больше, чем вы можете запускать одновременно; они по-прежнему полезны для удобства, даже если иногда им приходится стоять в очереди друг за другом.
источник
Дело в том, что, несмотря на отсутствие реального ускорения, когда количество потоков превышает количество ядер, вы можете использовать потоки для разделения частей логики, которые не должны быть взаимозависимыми.
Даже в умеренно сложном приложении использование одного потока попыток сделать все быстро делает хэш «потока» вашего кода. Одиночный поток тратит большую часть своего времени на опрос этого, проверки на это, условно вызывая подпрограммы по мере необходимости, и становится трудно увидеть что-либо, кроме болота мелких деталей.
Сравните это со случаем, когда вы можете выделить потоки задачам, чтобы, глядя на любой отдельный поток, вы могли видеть, что этот поток делает. Например, один поток может блокировать ожидание ввода из сокета, анализировать поток на сообщения, фильтровать сообщения и, когда приходит допустимое сообщение, передавать его другому рабочему потоку. Рабочий поток может работать с входными данными из ряда других источников. Код для каждого из них будет демонстрировать чистый, целенаправленный поток, без необходимости делать явные проверки того, что больше нечего делать.
Такое разделение работы позволяет вашему приложению полагаться на операционную систему, чтобы планировать, что делать дальше с процессором, поэтому вам не нужно делать явные условные проверки повсюду в вашем приложении о том, что может блокировать и что готово к обработке.
источник
Если поток ожидает ресурс (например, загрузка значения из ОЗУ в регистр, дисковый ввод-вывод, доступ к сети, запуск нового процесса, запрос к базе данных или ожидание ввода пользователя), процессор может работать с другой поток и вернитесь к первому потоку, когда ресурс станет доступен. Это сокращает время простоя ЦП, поскольку ЦП может выполнять миллионы операций вместо простоя.
Рассмотрим поток, которому необходимо считать данные с жесткого диска. В 2014 году типичное ядро процессора работает на частоте 2,5 ГГц и может выполнять 4 инструкции за цикл. При времени цикла 0,4 нс процессор может выполнять 10 инструкций за наносекунду. При типичном времени поиска на механическом жестком диске около 10 миллисекунд, процессор способен выполнить 100 миллионов инструкций за время, необходимое для чтения значения с жесткого диска. Могут быть значительные улучшения производительности с жесткими дисками с небольшим кешем (буфер 4 МБ) и гибридными дисками с несколькими ГБ хранилища, поскольку задержка данных при последовательном чтении или чтении из гибридной секции может быть на несколько порядков выше.
Ядро процессора может переключаться между потоками (стоимость приостановки и возобновления потока составляет около 100 тактовых циклов), в то время как первый поток ожидает ввода с высокой задержкой (что-то более дорогое, чем регистры (1 такт) и ОЗУ (5 наносекунд)). дисковый ввод-вывод, доступ к сети (задержка 250 мс), чтение данных с компакт-диска или медленной шины, или вызов базы данных. Наличие большего количества потоков, чем ядер, означает, что полезная работа может выполняться, пока решаются задачи с высокой задержкой.
В ЦП есть планировщик потоков, который назначает приоритет каждому потоку и позволяет потоку переходить в спящий режим, а затем возобновлять работу по истечении заданного времени. Задача планировщика потоков - уменьшить перегрузку, которая может возникнуть, если каждый поток выполнит всего 100 инструкций, прежде чем снова будет переведен в спящий режим. Накладные расходы на переключение потоков снизят общую полезную пропускную способность ядра процессора.
По этой причине вы можете захотеть разбить вашу проблему на разумное количество потоков. Если вы писали код для выполнения умножения матриц, создание одного потока на ячейку в выходной матрице могло бы быть чрезмерным, тогда как один поток на строку или на n строк в выходной матрице мог бы снизить накладные расходы на создание, приостановку и возобновление потоков.
Вот почему важно предсказание ветвлений. Если у вас есть оператор if, который требует загрузки значения из ОЗУ, но в теле операторов if и else используются значения, уже загруженные в регистры, процессор может выполнить одну или обе ветви до того, как условие будет оценено. Как только условие вернется, процессор применит результат соответствующей ветви и отбросит другой. Выполнение потенциально бесполезной работы здесь, вероятно, лучше, чем переключение на другой поток, что может привести к сбоям.
По мере того, как мы перешли от одноядерных процессоров с высокой тактовой частотой к многоядерным, при проектировании микросхем основное внимание было уделено увеличению количества ядер на кристалле, улучшению распределения ресурсов внутри кристалла между ядрами, улучшенным алгоритмам прогнозирования ветвлений, снижению накладных расходов на переключение потоков, и лучшее планирование потоков.
источник
Большинство ответов выше говорят о производительности и одновременной работе. Я собираюсь подойти к этому с другой стороны.
Возьмем, к примеру, упрощенную программу эмуляции терминала. Вам необходимо сделать следующее:
(Настоящие эмуляторы терминала делают больше, в том числе могут отображать на дисплее то, что вы вводите, но мы пока об этом не говорим.)
Теперь цикл для чтения с пульта прост, согласно следующему псевдокоду:
Цикл для мониторинга клавиатуры и отправки также прост:
Но проблема в том, что вы должны делать это одновременно. Теперь код должен выглядеть примерно так, если у вас нет потоковой передачи:
Логика, даже в этом намеренно упрощенном примере, который не принимает во внимание реальную сложность коммуникаций, довольно запутана. Однако при распараллеливании, даже на одном ядре, два цикла псевдокода могут существовать независимо, не чередуя свою логику. Поскольку оба потока будут в основном связаны с вводом-выводом, они не создают большой нагрузки на ЦП, хотя, строго говоря, они расходуют больше ресурсов ЦП, чем интегрированный цикл.
Конечно, реальное использование намного сложнее, чем описано выше. Но сложность интегрированного цикла возрастает экспоненциально по мере того, как вы добавляете дополнительные проблемы в приложение. Логика становится все более фрагментированной, и вам нужно начать использовать такие методы, как конечные автоматы, сопрограммы и т. Д., Чтобы сделать вещи управляемыми. Управляемый, но не читаемый. Многопоточность делает код более читабельным.
Так почему бы вам не использовать потоки?
Что ж, если ваши задачи связаны с процессором, а не с вводом-выводом, многопоточность фактически замедляет вашу систему. Пострадает производительность. Во многих случаях много. («Перебивание» - распространенная проблема, если вы отбрасываете слишком много потоков, связанных с ЦП. Вы тратите больше времени на изменение активных потоков, чем на выполнение содержимого самих потоков.) Кроме того, одна из причин, по которой приведенная выше логика заключается в так просто, что я сознательно выбрал упрощенный (и нереалистичный) пример. Если вы хотите отобразить то, что было напечатано на экране, вы получите новый мир боли, когда вы введете блокировку общих ресурсов. Имея только один общий ресурс, это не такая уж большая проблема, но она начинает становиться все большей и большей проблемой, поскольку у вас появляется больше ресурсов, которыми можно поделиться.
В конце концов, многопоточность касается многих вещей. Например, речь идет о том, чтобы сделать процессы, связанные с вводом-выводом, более отзывчивыми (даже если они в целом менее эффективны), как некоторые уже говорили. Это также касается упрощения логики (но только если вы минимизируете общее состояние). Речь идет о многих вещах, и вам нужно решить, перевешивают ли его преимущества его недостатки в каждом конкретном случае.
источник
Хотя вы, безусловно, можете использовать потоки для ускорения вычислений в зависимости от вашего оборудования, одно из их основных применений - выполнять несколько задач одновременно по причинам удобства пользователя.
Например, если вам нужно выполнить некоторую обработку в фоновом режиме, а также при этом реагировать на ввод пользовательского интерфейса, вы можете использовать потоки. Без потоков пользовательский интерфейс зависал бы каждый раз, когда вы пытались выполнить какую-либо тяжелую обработку.
Также см. Этот связанный вопрос: Практическое использование потоков
источник
Я категорически не согласен с утверждением @kyoryu о том, что идеальное число - один поток на процессор.
Подумайте об этом так: зачем у нас многопроцессорные операционные системы? На протяжении большей части компьютерной истории почти все компьютеры имели один процессор. Тем не менее, с 1960-х годов все «настоящие» компьютеры имели многозадачные (или многозадачные) операционные системы.
Вы запускаете несколько программ, так что одна может работать, а другие заблокированы для таких вещей, как ввод-вывод.
Давайте оставим в стороне споры о том, были ли версии Windows до NT многозадачными. С тех пор каждая реальная ОС была многозадачной. Некоторые не раскрывают его пользователям, но они все равно есть, например, слушают радио мобильного телефона, разговаривают с чипом GPS, принимают ввод от мыши и т. Д.
Потоки - это просто задачи, которые немного более эффективны. Нет принципиальной разницы между задачей, процессом и потоком.
ЦП - ужасная вещь, которую нельзя тратить, поэтому приготовьте много вещей, чтобы использовать его, когда сможете.
Я согласен с тем, что для большинства процедурных языков, C, C ++, Java и т. Д., Написание правильного поточно-безопасного кода - это большая работа. Сегодня на рынке представлены 6-ядерные процессоры, а неподалеку - 16-ядерные, поэтому я ожидаю, что люди отойдут от этих старых языков, поскольку многопоточность становится все более и более важным требованием.
Несогласие с @kyoryu - это просто ИМХО, остальное - факт.
источник
Представьте себе веб-сервер, который должен обслуживать произвольное количество запросов. Вы должны обслуживать запросы параллельно, потому что в противном случае каждый новый запрос должен ждать, пока все другие запросы не будут выполнены (включая отправку ответа через Интернет). В этом случае у большинства веб-серверов гораздо меньше ядер, чем количество запросов, которые они обычно обслуживают.
Это также упрощает задачу разработчика сервера: вам нужно только написать программу потока, которая обслуживает запрос, вам не нужно думать о хранении нескольких запросов, порядке их обслуживания и так далее.
источник
Многие потоки будут бездействовать, ожидая ввода пользователя, ввода-вывода и других событий.
источник
Потоки могут помочь повысить скорость отклика в UI-приложениях. Кроме того, вы можете использовать потоки, чтобы получить больше работы от ваших ядер. Например, на одном ядре у вас может быть один поток, выполняющий ввод-вывод, а другой - некоторые вычисления. Если бы он был однопоточным, ядро могло бы простаивать, ожидая завершения ввода-вывода. Это довольно высокоуровневый пример, но потоки определенно можно использовать, чтобы немного усложнить ваш процессор.
источник
Процессор или ЦП - это физический чип, который вставляется в систему. Процессор может иметь несколько ядер (ядро - это часть микросхемы, которая способна выполнять инструкции). Ядро может представляться операционной системе как несколько виртуальных процессоров, если оно способно одновременно выполнять несколько потоков (поток - это одна последовательность инструкций).
Процесс - это другое название приложения. Как правило, процессы независимы друг от друга. Если один процесс умирает, это не вызывает смерти другого процесса. Процессы могут обмениваться данными или совместно использовать ресурсы, такие как память или ввод-вывод.
У каждого процесса есть отдельное адресное пространство и стек. Процесс может содержать несколько потоков, каждый из которых может выполнять инструкции одновременно. Все потоки в процессе используют одно и то же адресное пространство, но каждый поток будет иметь свой собственный стек.
Надеюсь, эти определения и дальнейшие исследования с использованием этих основ помогут вам понять.
источник
Действительно, идеальное использование потоков - по одному на ядро.
Однако, если вы не используете исключительно асинхронный / неблокирующий ввод-вывод, высока вероятность того, что в какой-то момент у вас будут заблокированы потоки ввода-вывода, которые не будут использовать ваш процессор.
Кроме того, типичные языки программирования несколько затрудняют использование одного потока на процессор. Языки, разработанные для параллелизма (например, Erlang), могут упростить отказ от использования дополнительных потоков.
источник
Как устроены некоторые API, у вас нет выбора кроме как запускать их в отдельном потоке (что угодно с блокирующими операциями). Примером могут служить HTTP-библиотеки Python (AFAIK).
Обычно это не большая проблема (если это проблема, ОС или API должны поставляться с альтернативным асинхронным режимом работы, то есть :)
select(2)
, потому что это, вероятно, означает, что поток будет спать во время ожидания ввода / вывода. О завершение. С другой стороны, если что-то выполняет тяжелые вычисления, вы должны поместить это в отдельный поток, чем, скажем, поток графического интерфейса пользователя (если вам не нравится ручное мультиплексирование).источник
Я знаю, что это очень старый вопрос с множеством хороших ответов, но я здесь, чтобы указать на то, что важно в нынешних условиях:
Если вы хотите разработать приложение для многопоточности, вы не должны проектировать для конкретной настройки оборудования. Технологии ЦП развивались довольно быстро в течение многих лет, и количество ядер неуклонно растет. Если вы намеренно разрабатываете свое приложение так, чтобы в нем использовалось только 4 потока, то вы потенциально ограничиваете себя в восьмиъядерной системе (например). Сейчас в продаже имеются даже 20-ядерные системы, так что от такой конструкции однозначно больше вреда, чем пользы.
источник
В ответ на вашу первую догадку: многоядерные машины могут одновременно запускать несколько процессов, а не только несколько потоков одного процесса.
В ответ на ваш первый вопрос: суть нескольких потоков обычно заключается в одновременном выполнении нескольких задач в одном приложении. Классические примеры в сети - это программа электронной почты, отправляющая и получающая почту, а также веб-сервер, получающий и отправляющий запросы страниц. (Обратите внимание, что практически невозможно сократить такую систему, как Windows, до запуска только одного потока или даже только одного процесса. Запустите диспетчер задач Windows, и вы обычно увидите длинный список активных процессов, многие из которых будут выполнять несколько потоков. )
В ответ на ваш второй вопрос: большинство процессов / потоков не привязаны к ЦП (т. Е. Не работают постоянно и непрерывно), а вместо этого останавливаются и часто ждут завершения ввода-вывода. Во время этого ожидания другие процессы / потоки могут работать без «кражи» кода ожидания (даже на одноядерной машине).
источник
Поток - это абстракция, которая позволяет вам писать код так же просто, как последовательность операций, блаженно не подозревая о том, что код выполняется с чередованием с другим кодом, или припаркован в ожидании ввода-вывода, или (может быть, несколько более осведомлен), ожидая другого потока события или сообщения.
источник
Дело в том, что подавляющее большинство программистов не понимают, как проектировать конечный автомат. Возможность поместить все в отдельный поток освобождает программиста от необходимости думать о том, как эффективно представлять состояние различных выполняемых вычислений, чтобы их можно было прервать, а затем возобновить.
В качестве примера рассмотрим сжатие видео - задачу с очень интенсивным использованием ЦП. Если вы используете графический интерфейс, вы, вероятно, хотите, чтобы интерфейс оставался отзывчивым (показывал прогресс, отвечал на запросы отмены, изменение размера окна и т. Д.). Таким образом, вы разрабатываете программное обеспечение кодировщика для обработки большого блока (одного или нескольких кадров) за раз и запускаете его в собственном потоке, отдельно от пользовательского интерфейса.
Конечно, как только вы поймете, что было бы неплохо иметь возможность сохранить текущее состояние кодирования, чтобы вы могли закрыть программу для перезагрузки или поиграть в ресурсоемкую игру, вы поймете, что вам следовало научиться создавать конечные автоматы из начало. Либо так, либо вы решите разработать совершенно новую проблему спящего режима вашей ОС, чтобы вы могли приостанавливать и возобновлять отдельные приложения на диск ...
источник