Что представляет собой правильное использование потоков в программировании?

13

Я устал слышать, как люди рекомендуют использовать только один поток на процессор, в то время как многие программы используют до 100 на процесс! возьмем для примера некоторые распространенные программы

vb.net ide uses about 25 thread when not debugging
System uses about 100
chrome uses about 19
Avira uses more than about 50

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

кузнец
источник
7
Эта рекомендация для широкой. Ограничение в один поток на процессор подходит только для приложений, связанных с вычислениями. Большинство программ связаны с вводом-выводом, будь то сетевой трафик, доступ к диску или даже ОЗУ. Вот почему веб-серверы, базы данных и т. Д. Имеют пулы потоков с гораздо большим количеством потоков, чем процессорных ядер.
Килиан Фот
2
«Мне почти каждый раз напоминают, что я не должен использовать более одного потока на процессор»? Можете ли вы опубликовать ссылки или примеры? Почти каждый раз?
С.Лотт
2
«... люди рекомендуют использовать только один поток на процесс». Кто эти люди? Планирование значительно продвинулось со времен темных веков.
Рейн Хенрикс
2
Вы не должны иметь более одного потока пользовательского интерфейса на процесс.
Слух
3
@Billy ONeal, ваше редактирование сделало вопрос бессмысленным
SK-logic

Ответы:

22

вы должны использовать только один поток на процессор,

Возможно, в HPC, где вы хотите максимальной эффективности - но в остальном самая глупая вещь, которую я слышал сегодня!

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

Для веб-сервера может быть целесообразно запускать поток для каждого входящего соединения (хотя существуют и более эффективные способы для сильно загруженных серверов).

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

Мартин Беккет
источник
9
Теперь ты меня удивляешь, какая самая глупая вещь, которую ты когда-либо слышал!
Майкл К
3
@ Майкл - я преподавал старшекурсникам и работал над оборонными контрактами - ты не поверишь самым глупым вещам, которые я слышал!
Мартин Беккет
1
Мы видели их на TheDailyWTF.com?
FrustratedWithFormsDesigner
я не могу найти их сейчас, но посмотрите на эту ссылку social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/…
Смит,
2
У приложения должно быть не более одного потока с привязкой к ЦП на процессор. Связанные с IO потоки не являются большой проблемой (кроме памяти, которую они потребляют), и важно помнить, что приложения могут быть ограничены, чтобы использовать только подмножество процессоров системы; в конце концов, это (обычно) компьютер пользователя / администратора, а не компьютер программиста.
Donal Fellows
2

Рекомендация «один поток на ядро» применяется, когда целью является скорость посредством параллельного выполнения.

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

Майк Данлавей
источник
1
Обработка, связанная с вводом-выводом, может выполняться как один поток на источник событий, или несколько источников событий могут быть мультиплексированы в один поток. Мультиплексный код обычно более сложен и эффективен.
Donal Fellows
2

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

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

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

Ира Бакстер
источник
Просто имейте в виду, что этот стиль многопоточности может привести к некоторым программам со странной архитектурой. Я видел 4-х поточную программу, в которой был поток для чтения записей из таблицы БД, поток для записи преобразованных записей в сокет, поток для чтения ответов на эти записи в сокеты (которые вернулись не в порядке) и асинхронно), и поток, чтобы изменить исходную запись БД с ответом. Произошли неинтуитивные ошибки.
Брюс Эдигер
Одна точка зрения заключается в том, что этот стиль создает странные программы. Другое мнение - это естественный стиль, который должны были иметь программы. Не знаю о «неинтуитивных» условиях ошибки; если у вас много чего происходит, и одна из них получает ошибку, то проверка правильности ее распространения в асинхронных вычислениях является проблемой для многих языков [глупо, исключения Java не определены на границах потоков], но не проблема со стилем программы. (Наш язык программирования PARLANSE [см. Мою биографию] аккуратно обрабатывает исключения через границы потоков, поэтому это можно сделать правильно.).
Ира Бакстер
1

Эмпирическое правило для потоков заключается в том, что для каждого «исполнительного блока», доступного на компьютере, требуется по крайней мере один «активный» (способный выполнять свои команды немедленно при заданном времени процессора). «Исполнительный блок» - это один процессор логических команд, поэтому четырехъядерный четырехъядерный гиперпоточный сервер Xeon будет иметь 32 EU (4 микросхемы, 4 ядра на микросхему, каждая гиперзаходная). Ваш средний Core i7 будет иметь 8.

Один поток в ЕС - это наиболее полное использование мощности процессора при условии, что потоки всегда будут в рабочем состоянии; это почти никогда не происходит, поскольку потокам необходим доступ к некешированной памяти, жесткому диску, сетевым портам и т. д., которых они должны ждать, и которые не требуют активного внимания ЦП для выполнения. Таким образом, вы можете еще больше повысить общую эффективность, добавив больше потоков в очередь и рваясь в путь. Это действительно стоит денег; когда ЦП переключает поток, он должен кэшировать регистры потока, указатель выполнения и другую информацию о состоянии, которая обычно хранится во внутренних документах ЕС и очень быстро доступна, что позволяет другим ЕС в этом чипе ЦП забрать его. Также требуется, чтобы потоки в ОС решали, на какой поток следует переключиться. Наконец, когда ЕС переключает темы, он теряет прирост производительности конвейерной обработки, которую использует большинство процессорных архитектур; он должен промыть конвейер перед переключением потоков. Но, поскольку все это в среднем занимает гораздо меньше времени, чем простое ожидание, пока жесткий диск или даже ОЗУ не вернутся с информацией, это стоит затрат.

Тем не менее, как правило, когда вы вдвое превышаете число «активных» потоков по сравнению с ЕС, ОС начинает тратить больше потоков планирования времени ЕС, а ЕС тратит больше времени на переключение между ними, чем фактически тратит на запуск активных потоков. программ. Это точка диссоциации масштаба; на самом деле многопоточный алгоритм будет работать дольше, если в этот момент вы добавите дополнительный поток.

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

Keiths
источник
Если N - это количество потоков, а U - количество единиц, ОП подвергает сомнению правило «N = U». Вы смягчаете это к правилу "U <= N <= 2 U". Я бы пошел немного дальше и сказал бы, что «N <= c U» для «достаточно маленькой» константы (известной программисту) c приемлемо (если тесты показывают приемлемую производительность). Я был бы очень обеспокоен, если количество потоков может вырасти до потенциально неограниченного количества.
5gon12eder
1

Вы должны использовать один поток для:

Каждый процессор нужно держать занятым.

Каждый ввод / вывод, который вы можете одновременно использовать, не может быть выполнен неблокирующим. (Например, читает с локального диска.)

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

Несколько дополнительных для защиты от неожиданных блокировок, таких как сбои страниц.

Несколько дополнительных для защиты от ожидаемой блокировки, которую не стоит оптимизировать, например, в некритическом коде. (Например, если вам может понадобиться выполнить запрос DNS очень редко, возможно, не стоит делать запросы DNS асинхронно. Просто создайте несколько дополнительных потоков и упростите свою жизнь.)

Если вы следуете правилу «один поток на процессор», то весь ваш код критичен к производительности. Любой код, который по какой-то причине блокирует, означает, что ваш процесс не может использовать этот процессор. Это делает программирование намного сложнее без веской причины.

Дэвид Шварц
источник
0

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

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

Питер Смит
источник
0

Приложения, которые вы упоминаете, редко запускают все эти десятки потоков одновременно. Большинство из них просто сидят там, потому что они находятся в пуле потоков . Приложение отправляет различные задачи в очередь, которая очищается потоками в пуле потоков.

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

Joonas Pulakka
источник