Что такое процесс и поток?
У меня есть вопрос, связанный с пониманием процессов и потоков в операционных системах. Я прочитал множество материалов на эту тему и продолжаю изучать её, но все они описывают процессы и потоки в довольно "абстрактной" форме. Это приводит к большим теоретическим elaborations об их поведении и логической организации.
Меня интересует, что они физически представляют собой? На мой взгляд, процессы и потоки — это просто какие-то "структуры данных" в памяти, которые поддерживаются и используются кодом ядра для облегчения выполнения программы. Например, операционная система использует структуру данных процесса (PCB) для описания аспектов процесса, назначенного для определенной программы, таких как приоритет, адресное пространство и так далее.
Я правильно понимаю?
5 ответ(ов)
Первое, что вам нужно знать, чтобы понять разницу между процессом и потоком, это то, что процессы не выполняются, выполняются потоки.
Итак, что такое поток? Ближе всего к объяснению будет такое определение: поток — это состояние выполнения, то есть комбинация регистров ЦП, стека и т.д. Доказать это можно, заглянув в отладчик в любой момент времени. Что вы увидите? Стек вызовов и набор регистров. Вот и все. Это и есть поток.
Теперь, что такое процесс. Это своего рода абстрактная "контейнерная" сущность для запуска потоков. Для операционной системы на первом приближении процесс — это сущность, которой выделяется определенное виртуальное пространство памяти, а также назначаются некоторые системные ресурсы (такие как дескрипторы файлов, сетевые сокеты) и т.д.
Как они работают вместе? Операционная система создает "процесс", резервируя для него ресурсы и запуская "основной" поток. Этот поток затем может создавать новые потоки. Эти потоки находятся в одном процессе. Они могут в той или иной степени делить эти ресурсы (например, может понадобиться блокировка, чтобы не помешать другим потокам и т.д.). После этого ОС, как правило, отвечает за поддержание этих потоков "внутри" этого виртуального пространства памяти (обнаруживая и предотвращая попытки доступа к памяти, которая не "принадлежит" этому процессу), предоставляя некоторый тип планирования потоков, чтобы они могли выполняться "один за другим, а не только один постоянно".
Когда вы запускаете исполняемый файл, например, notepad.exe, создается отдельный процесс. Этот процесс может порождать другие процессы, но в большинстве случаев для каждого запущенного вами исполняемого файла существует единственный процесс. Внутри процесса может быть несколько потоков. Обычно в начале существует один поток, который стартует с "точки входа" программы, обычно это функция main
. Инструкции выполняются последовательно, как человек, у которого только одна рука — поток может выполнять лишь одно действие в одно время, прежде чем перейдет к следующему.
Первый поток может создать дополнительные потоки. Каждый дополнительный поток имеет свою собственную точку входа, которая обычно определяется с помощью функции. Процесс можно рассматривать как контейнер для всех потоков, которые были созданы в его рамках.
Это довольно упрощенное объяснение. Я мог бы углубиться в детали, но, скорее всего, это пересеклось бы с тем, что вы найдете в ваших учебниках.
Дополнение: Вы заметите, что в моем объяснении много "обычно", так как иногда встречаются редкие программы, которые работают совершенно иначе.
Одной из причин, по которой практически невозможно описать потоки и процессы непрерывно, является то, что они являются абстракциями.
Их конкретные реализации могут сильно отличаться.
Например, сравните процесс в Erlang и процесс в Windows: процесс в Erlang очень легковесен и часто занимает меньше 400 байт. Вы можете запустить 10 миллионов процессов на не самом современном ноутбуке без каких-либо проблем. Они запускаются очень быстро, заканчиваются очень быстро, и предполагается, что вы будете использовать их для очень коротких задач. Каждый процесс Erlang имеет свой собственный сборщик мусора. Процессы Erlang никогда не могут совместно использовать память.
Процессы Windows весьма тяжелы, иногда занимая сотни мегабайт. Вы можете запустить только несколько тысяч из них на мощном сервере, если вам повезет. Они запускаются и завершаются довольно медленно. Процессы Windows являются единицами приложений, таких как IDE, текстовые редакторы или процессоры, поэтому от них обычно ожидается, что они будут жить довольно долго (по крайней мере несколько минут). У них есть собственное адресное пространство, но нет сборщика мусора. Процессы Windows могут совместно использовать память, хотя по умолчанию этого не происходит.
С потоком дела обстоят аналогично: поток NPTL в Linux на x86 может занимать всего 4 кБ, и с некоторыми ухищрениями вы можете запустить более 800000 потоков на 32-битной x86-машине. Машина безусловно будет пригодной для использования с тысячами, а возможно, и десятками тысяч потоков. Поток .NET CLR имеет минимальный размер около 1 МБ, что означает, что всего 4000 таких потоков займут всё ваше адресное пространство на 32-битной машине. Таким образом, хотя 4000 потоков NPTL в Linux, как правило, не являются проблемой, вы не сможете даже запустить 4000 потоков .NET CLR, потому что исчерпаете память прежде.
Процессы и потоки ОС также реализованы совершенно по-разному в различных операционных системах. Основные два подхода заключаются в том, что ядро знает только о процессах. Потоки реализованы библиотеками пользовательского пространства, без какого-либо знания о ядре. В этом случае также есть два подхода: 1:1 (каждый поток соответствует одному процессу ядра) или m:n (m потоков соответствуют n процессам, где обычно m > n и часто n == количеству ЦП). Это был ранний подход, который использовался во многих операционных системах после изобретения потоков. Тем не менее, он обычно считается неэффективным и был заменён почти на всех системах вторым подходом: потоки реализуются (по крайней мере частично) в ядре, и теперь оно знает о двух различных сущностях — потоках и процессах.
Операционная система, который использует третий подход — это Linux. В Linux потоки не реализованы в пользовательском пространстве и не в ядре. Вместо этого ядро предоставляет абстракцию как для потока, так и для процесса (а также для нескольких других вещей), называемую задачей (Task). Задача — это сущность, управляемая планировщиком ядра, которая несет с собой набор флагов, определяющих, какие ресурсы она делит с «соседями», а какие являются частными.
В зависимости от того, как вы поставите эти флаги, вы получите либо поток (делятся почти всем), либо процесс (делятся всем системным ресурсами, такими как системные часы, пространство файловой системы, пространством сети, пространство идентификаторов пользователей, пространство идентификаторов процессов, но не делятся адресным пространством). Но вы также можете получить некоторые другие довольно интересные вещи. Вы можете легко получить тюрьмы в стиле BSD (основанное на тех же флагах, что и процесс, но не делящее файловую или сетевую пространство). Либо вы можете получить то, что другие ОС называют контейнером виртуализации или зоной (как тюрьма, но не делящая пространствами UID и PID, а также системные часы). С тех пор, как несколько лет назад была введена технология под названием KVM (Kernel Virtual Machine), вы даже можете получить полноценную виртуальную машину (не деля ничего, даже таблицы страниц процессора). [Что круто в этом, так это то, что вы можете повторно использовать высокоэффективный и зрелый планировщик задач в ядре для всех этих задач. Одной из причин, по которой виртуальная машина Xen часто критиковали, было плохое качество её планировщика. У разработчиков KVM гораздо лучший планировщик, чем у Xen, и самое лучшее — они даже не пришлось писать для этого ни одной строчки кода!]
Таким образом, в Linux производительность потоков и процессов гораздо ближе, чем в Windows и во многих других системах, потому что в Linux они на самом деле одно и то же. Это означает, что модели использования очень различаются: в Windows вы обычно принимаете решение между использованием потока и процесса, исходя из их «веса»: могу ли я позволить себе использовать процесс или должен использовать поток, даже если на самом деле не хочу делить состояние? В Linux (и обычно в Unix в целом) вы принимаете решение на основе семантики: хочу ли я на самом деле делить состояние или нет?
Одна из причин, почему процессы, как правило, легче в Unix, чем в Windows, заключается в различном использовании: в Unix процессы являются основной единицей как параллелизма, так и функциональности. Если вы хотите использовать параллелизм, вы используете несколько процессов. Если ваше приложение можно разделить на несколько независимых частей, вы используете несколько процессов. Каждое приложение выполняет ровно одну задачу и только эту задачу. Даже простой одно строчный скрипт оболочки часто включает в себя десятки или сотни процессов. Приложения обычно состоят из множества, часто недолговечных процессов.
В Windows потоки являются основной единицей параллелизма, а компоненты COM или объекты .NET являются основной единицей функциональности. Приложения, как правило, состоят из единого процесса, работающего долго.
Таким образом, они используются для совершенно различных целей и имеют очень разные проектные цели. Дело не в том, что одно лучше или хуже другого, а в том, что они настолько различны, что общие характеристики могут быть описаны лишь очень абстрактно.
По сути, единственные несколько вещей, которые вы можете сказать о потоках и процессах, это:
- Потоки принадлежат процессам
- Потоки легче, чем процессы
- Потоки делят большую часть состояния друг с другом
- Процессы делят значительно меньше состояния, чем потоки (в частности, они обычно не делят память, если это не запрашивается специально)
Я бы сказал, что:
Процесс имеет адресное пространство, открытые файлы и т.д., а также одну или несколько потоков.
Поток — это последовательность инструкций, которая может планироваться системой на процессоре.
Потоки – это структуры памяти в планировщике операционной системы, как вы и упомянули. Потоки указывают на начало некоторых инструкций в памяти и обрабатывают их, когда планировщик решает, что это необходимо. Пока поток выполняется, работает аппаратный таймер. Как только он достигает заданного времени, вызывается прерывание. После этого аппаратное обеспечение прекращает выполнение текущей программы и вызывает зарегистрированную функцию-обработчик прерывания, которая будет частью планировщика, чтобы уведомить о том, что текущий поток завершил выполнение.
Безопасно ли делать fork из потока?
Программно определить количество ядер на машине
Что такое бес блокировочное многопоточное программирование?
Что нужно знать при погружении в многопоточное программирование на C++
Как удалить элемент из std::vector<> по индексу?