Потоки против процессов в Linux

253

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

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

user17918
источник
Есть ли разница с Linux 2.4?
mouviciel
3
Разница между процессами и потоками в Linux 2.4 заключается в том, что потоки разделяют больше частей своего состояния (адресного пространства, файловых дескрипторов и т. Д.), Чем процессы, которые обычно этого не делают. NPTL в Linux 2.6 делает это немного более понятным, предоставляя им «группы потоков», которые немного похожи на «процессы» в win32 и Solaris.
MarkR
6
Параллельное программирование сложно. Если вам не нужна очень высокая производительность, наиболее важным аспектом вашего компромисса часто будет трудность отладки . В этом отношении процессы значительно облегчают решение, поскольку все коммуникации являются явными (их легко проверить, зарегистрировать и т. Д.). Напротив, общая память потоков создает миллиарды мест, где один поток может ошибочно влиять на другой.
Латц Пречелт
1
@LutzPrechelt - параллельное программирование может быть как многопоточным, так и многопроцессным. Я не понимаю, почему вы предполагаете, что параллельное программирование является только многопоточным. Это может быть из-за определенных языковых ограничений, но в целом это может быть и другое.
Янкит
2
Я ссылаюсь на то, что Лутц просто заявил, что параллельное программирование сложно в зависимости от того, какой процесс выбран - процесс или потоки, - но параллельное программирование с использованием процессов во многих случаях облегчает отладку.
user2692263

Ответы:

322

В Linux используется модель потоков 1-1, в которой (для ядра) нет различий между процессами и потоками - все это просто выполняемая задача. *

В Linux системный вызов cloneклонирует задачу с настраиваемым уровнем общего доступа, среди которых:

  • CLONE_FILES: использовать одну и ту же таблицу дескрипторов файлов (вместо создания копии)
  • CLONE_PARENT: не устанавливать отношения родитель-потомок между новой задачей и старой (в противном случае child's getppid()= parent's getpid())
  • CLONE_VM: использовать то же пространство памяти (вместо создания копии COW )

fork()вызывает clone(наименьшее совместное использование )и pthread_create()вызовы clone(наиболее общего доступа ). **

forkСтоимость pthread_createкопирования немного выше, чем копирование таблиц и создание сопоставлений COW для памяти, но разработчики ядра Linux постарались (и преуспели) в минимизации этих затрат.

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

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


* Упрощенный CLONE_THREADвызывает CLONE_SIGHANDпередачу сигналов для совместного использования (что необходимо , что разделяет таблицу обработчиков сигналов).

** Упрощено. Существуют SYS_forkи SYS_cloneсистемные вызовы, и системные вызовы, но в ядре обе sys_forkи sys_cloneявляются очень тонкими оболочками для одной и той же do_forkфункции, которая сама по себе является тонкой оболочкой copy_process. Да, термины process, threadи taskиспользуются довольно взаимозаменяемо в ядре Linux ...

ephemient
источник
6
Я думаю, что нам не хватает 1 очко. Если вы делаете несколько процессов для своего веб-сервера, то вам нужно написать другой процесс, чтобы открыть сокет и передать «работу» другим потокам. Threading предлагает один процесс несколько потоков, чистый дизайн. Во многих ситуациях поток просто естественен, а в других ситуациях новый процесс просто естественен. Когда проблема попадает в «серую область», другие компромиссы, как объясняет ephemient, становятся важными.
Саураб
26
@Saurabh Не совсем. Вы можете легко socket, bind, listen, fork, а затем несколько процессов acceptсоединения на одном сокете. Процесс может перестать принимать, если он занят, и ядро ​​направит входящие соединения другому процессу (если никто не слушает, ядро ​​будет стоять в очереди или отбрасываться, в зависимости от listenотставания). У вас нет намного большего контроля над распределением работы, чем обычно, но обычно этого достаточно!
Эфимент
2
@Bloodcount Все процессы / потоки в Linux создаются одним и тем же механизмом, который клонирует существующий процесс / поток. Флаги переданы, чтобы clone()определить, какие ресурсы являются общими. Задача также может потребовать unshare()ресурсы в любой более поздний момент времени.
Эфимент
4
@KarthikBalaguru В самом ядре есть task_structдля каждой задачи. Это часто называют «процессом» во всем коде ядра, но оно соответствует каждому выполняемому потоку. Нет process_struct; если куча task_structs связана друг с другом своим thread_groupсписком, то это один и тот же «процесс» для пользовательского пространства. Есть небольшая специальная обработка «потоков», например, все дочерние потоки останавливаются на fork и exec, и в них отображается только «основной» поток ls /proc. Каждый поток доступен через /proc/pid, независимо от того, включен он в список /procили нет.
Эфимент
5
@KarthikBalaguru Ядро поддерживает континуум поведения между потоками и процессами; например, clone(CLONE_THREAD | CLONE_VM | CLONE_SIGHAND))даст вам новый «поток», который не разделяет рабочий каталог, файлы или блокировки, в то время как clone(CLONE_FILES | CLONE_FS | CLONE_IO)даст вам «процесс», который делает. Основная система создает задачи путем клонирования; fork()и pthread_create()являются просто библиотечными функциями, которые вызывают по- clone()разному (как я написал в этом ответе).
Эфимент
60

Linux (да и вообще Unix) дает вам третий вариант.

Вариант 1 - процессы

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

Вариант 2 - темы

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

Вариант 3 - вилка

Доступно только под Linux / Unix, это немного отличается. Разветвленный процесс на самом деле является своим собственным процессом со своим собственным адресным пространством - дочерний элемент не может (обычно) ничего сделать, чтобы повлиять на адресное пространство своего родителя или братьев и сестер (в отличие от потока), поэтому вы получаете дополнительную устойчивость.

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

Рассмотрим программу веб-сервера, которая состоит из двух этапов:

  1. Чтение данных конфигурации и времени выполнения
  2. Обслуживать запросы страниц

Если вы используете потоки, шаг 1 будет выполнен один раз, а шаг 2 - в нескольких. Если вы использовали «традиционные» процессы, шаги 1 и 2 должны были бы повторяться для каждого процесса, а память для хранения данных конфигурации и времени выполнения дублировалась. Если вы использовали fork (), то вы можете выполнить шаг 1 один раз, а затем fork (), оставив данные и конфигурацию времени выполнения в памяти, нетронутыми, а не скопированными.

Так что на самом деле есть три варианта.

MarkR
источник
7
Разветвление @Qwertie не очень круто, оно тонко разбивает множество библиотек (если вы используете их в родительском процессе). Это создает неожиданное поведение, которое смущает даже опытных программистов.
MarkR
2
@MarkR не могли бы вы привести несколько примеров или ссылку на то, как разветвление разбивает библиотеку и создает неожиданное поведение?
Эхтеш Чоудхури
18
Если процесс разветвляется с открытым соединением mysql, происходят плохие вещи, так как сокет разделяется между двумя процессами. Даже если только один процесс использует соединение, другой останавливает его закрытие.
MarkR
1
Системный вызов fork () определяется POSIX (что означает, что он доступен в любых системах Unix), если вы использовали базовый Linux API, который является системным вызовом clone (), то у вас на самом деле есть еще больше вариантов в Linux, чем только три ,
Ли Райан
2
@MarkR Совместное использование сокета разработано специально. Кроме того, любой из процессов может закрыть сокет с помощью linux.die.net/man/2/shutdown перед вызовом close () для сокета.
Лелантран
53

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

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

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

Адам Розенфилд
источник
9
Ответ Адама послужит хорошим брифингом для руководителей. Для более подробной информации, MarkR и ephemient дают хорошие объяснения. Очень подробное объяснение с примерами можно найти по адресу cs.cf.ac.uk/Dave/C/node29.html, но, похоже, оно несколько устарело по частям.
CyberFonic
2
CyberFonic это верно для Windows. Как говорит ephemient под Linux, процессы не тяжелее. А в Linux все механизмы, доступные для связи между потоками (futex, общая память, каналы, IPC), также доступны для процессов и работают с одинаковой скоростью.
Рассел Стюарт
IPC сложнее в использовании, но что если кто-то использует «разделяемую память»?
abhiarora
11

Другие обсуждали соображения.

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

dmckee --- котенок экс-модератора
источник
9

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

В современном Linux (2.6.x) нет большой разницы в производительности между переключением контекста процесса по сравнению с потоком (только поток MMU является дополнительным для потока). Существует проблема с общим адресным пространством, что означает, что неисправный указатель в потоке может повредить память родительского процесса или другого потока в том же адресном пространстве.

Процесс защищен MMU, поэтому неисправный указатель просто вызовет сигнал 11 и не повредит.

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

Как вы думаете, почему потоки имеют такой большой прирост производительности в Linux? У вас есть данные для этого, или это просто миф?

robert.berger
источник
1
Да, у меня есть некоторые данные. Я запустил тест, который создает 100 000 процессов и тест, который создает 100 000 потоков. Версия потока работала примерно в 9 раз быстрее (17,38 секунды для процессов, 1,93 для потоков). Теперь это только тестирует время создания, но для краткосрочных задач время создания может быть ключевым.
user17918
4
@ user17918 - Можете ли вы поделиться кодом, который вы использовали для вычисления вышеупомянутых временных интервалов ..
codingfreak
одно большое различие, с процессами ядро ​​создает таблицу страниц для каждого процесса, и все они используют только одну таблицу страниц, так что я думаю, что это нормально, потоки быстрее, чем процессы
c4f4t0r
Еще один простой способ взглянуть на это - TCB гораздо меньше, чем PCB, и поэтому очевидно, что переключение контекста процесса с использованием PCB будет занимать немного больше времени, чем переключение потоков.
Картик Балагуру,
5

Насколько тесно связаны ваши задачи?

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

Роберт
источник
4

Чтобы еще больше усложнить ситуацию, существует такая вещь, как локальное хранилище потоков и общая память Unix.

Локальное хранилище потоков позволяет каждому потоку иметь отдельный экземпляр глобальных объектов. Единственный раз, когда я использовал его, был при создании среды эмуляции в linux / windows для кода приложения, работающего в RTOS. В RTOS каждая задача была процессом с собственным адресным пространством, в среде эмуляции каждая задача была потоком (с общим адресным пространством). Используя TLS для таких вещей, как синглеты, мы смогли создать отдельный экземпляр для каждого потока, как в «реальной» среде RTOS.

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

KeyserSoze
источник
1
Я использовал локальное хранилище потоков для сбора некоторой статистики, когда в последний раз писал программу для многопоточных сетей: каждый поток записывал в свои собственные счетчики, блокировки не нужны, и только при наличии сообщений каждый поток объединял свою статистику в глобальные итоги. Но да, TLS не очень часто используется или необходим. Общая память, с другой стороны ... в дополнение к эффективной отправке данных вы также можете делиться семафорами POSIX между процессами, помещая их в общую память. Это довольно удивительно.
эпимент
4

В моей недавней работе с LINUX важно знать о библиотеках. Если вы используете потоки, убедитесь, что все библиотеки, которые вы можете использовать между потоками, являются поточно-ориентированными. Это обожгло меня пару раз. Примечательно, что libxml2 не поддерживает потоки из коробки. Он может быть скомпилирован с поддержкой потоков, но это не то, что вы получаете при установке aptitude.

aal8
источник
3

Я должен согласиться с тем, что вы слышали. Когда мы тестируем наш кластер ( xhplи тому подобное), мы всегда получаем значительно лучшую производительность с процессами над потоками.</anecdote>

eduffy
источник
3

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

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

hlovdal
источник
2

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

Neal Aise
источник
Что ты имеешь в виду без выгоды? Как насчет выполнения тяжелых вычислений в потоке GUI? Перемещение их в параллельный поток будет намного удобнее с точки зрения пользовательского опыта, независимо от загрузки процессора.
olegst
2

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

Юбин Энтони Тикаттил
источник
2

Я думаю, что все проделали отличную работу, отвечая на ваш вопрос. Я просто добавляю больше информации о потоке против процесса в Linux, чтобы уточнить и обобщить некоторые предыдущие ответы в контексте ядра. Итак, мой ответ касается специфичного для ядра кода в Linux. Согласно документации ядра Linux, нет четкого различия между потоком и процессом, за исключением того, что поток использует общее виртуальное адресное пространство в отличие от процесса. Также обратите внимание, что ядро ​​Linux использует термин «задача» для обозначения процесса и потока в целом.

«Нет внутренних структур, реализующих процессы или потоки, вместо этого есть структура task_struct, которая описывает абстрактный блок планирования, называемый задачей»

Кроме того, согласно Линусу Торвальдсу, вы НЕ должны вообще думать о процессе и потоке, поскольку он слишком ограничен, и единственное отличие заключается в COE или контексте выполнения в терминах «отделения адресного пространства от родительского» или общего адресного пространства. На самом деле он использует пример веб - сервера , чтобы сделать его пункт здесь (который настоятельно рекомендую прочитать).

Полный кредит документации ядра Linux

grepit
источник
-3

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

Также учтите тот факт, что переключение контекста между потоками намного дешевле, чем переключение контекста между процессами.

Я не вижу причин явно использовать отдельные процессы, если у вас нет веских причин для этого (безопасность, проверенные тесты производительности и т. Д.)

Ювал Адам
источник
3
У меня есть представитель для редактирования, но я не совсем согласен. Переключение контекста между процессами в Linux почти так же дешево, как переключение контекста между потоками.
Эфимент