Процессы и треды

Понятие процесса было введено для реализации идей мультипрограммирования. Напомним, в свое время различали термины «мультизадачность» и «мультипро­граммирование». Таким образом, для реализации «мультизадачности» в ее ис­ходном толковании необходимо было тоже ввести соответствующую сущность. Такой сущностью и стали так называемые «легковесные» процессы, или, как их теперь преимущественно называют, — потоки или треды (нити). Рассмотрим эти понятия подробнее.

Когда говорят о процессах (process), то тем самым хотят отметить, что операци­онная система поддерживает их обособленность: у каждого процесса имеется свое виртуальное адресное пространство, каждому процессу назначаются свои ресур­сы — файлы, окна, семафоры и т. д. Такая обособленность нужна для того, чтобы защитить один процесс от другого, поскольку они, совместно используя все ре­сурсы вычислительной системы, конкурируют друг с другом. В общем случае процессы просто никак не связаны между собой и могут принадлежать даже раз­ным пользователям, разделяющим одну вычислительную систему. Другими сло­вами, в случае процессов ОС считает их совершенно несвязанными и независимыми. При этом именно ОС берет на себя роль арбитра в конкуренции между процессами по поводу ресурсов.

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

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

Особенно эффективно можно использовать многопоточность для выполнения распределенных приложений; например, многопоточный сервер может парал­лельно выполнять запросы сразу нескольких клиентов. Как известно, операци­онная система OS/2 одной из первых среди ОС, используемых на ПК, ввела многопоточность. В середине девяностых годов для этой ОС было создано очень большое количество приложений, в которых использование механизмов много­поточной обработки реально приводило к существенно большей скорости вы­полнения вычислений.

Итак, сущность «поток» была введена для того, чтобы именно с помощью этих единиц распределять процессорное время между возможными работами. Сущ­ность «процесс» предполагает, что при диспетчеризации нужно учитывать все ресурсы, закрепленные за ним. А при манипулировании тредами можно менять только контекст задачи, если мы переключаемся с одной задачи на другую в рам­ках одного процесса. Все остальные вычислительные ресурсы при этом не затра­гиваются. Каждый процесс всегда состоит по крайней мере из одного потока, и только если имеется внутренний параллелизм, программист может «расще­пить» один тред на несколько параллельных.

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

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

Как мы уже знаем, все треды имеют одно и то же виртуальное адресное про­странство своего процесса. Это означает, что они разделяют одни и те же гло­бальные переменные. Поскольку каждый тред может иметь доступ к каждому виртуальному адресу, один тред может использовать стек другого треда. Между потоками нет полной защиты, так как это, во-первых, невозможно, а во-вторых, не нужно. Все потоки одного процесса всегда решают общую задачу одного поль­зователя, и механизм потоков используется здесь для более быстрого решения задачи путем ее распараллеливания. При этом программисту очень важно полу­чить в свое распоряжение удобные средства организации взаимодействия частей одной программы. Повторим, что кроме разделения адресного пространства, все треды разделяют также набор открытых файлов, используют общие устройства, выделенные процессу, имеют одни и те же наборы сигналов, семафоры и т. п. А что у тредов будет их собственным? Собственными являются программный счетчик, стек, рабочие регистры процессора, потоки-потомки, состояние.

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

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

Для того чтобы можно было эффективно организовать параллельное выполне­ние рассмотренных сущностей (процессов и тредов), в архитектуру современных процессоров включена возможность работать со специальной информационной структурой, описывающей ту или иную сущность. Для этого уже на уровне архи­тектуры микропроцессора используется понятие «задача» (task). Оно как бы объединяет в себе обычный и «легковесный» процессы. Это понятие и поддер­живаемая для него на уровне аппаратуры информационная структура позволяют в дальнейшем при разработке операционной системы построить соответствую­щие дескрипторы как для процесса, так и для треда. Отличаться эти дескрипто­ры будут прежде всего тем, что дескриптор треда может хранить только контекст приостановленного вычислительного процесса, тогда как дескриптор процесса (process) должен уже содержать поля, описывающие тем или иным способом ре­сурсы, выделенные этому процессу. Другими словами, тот же task state segment (сегмент состояния задачи) используется как основа для дескриптора процесса. Каждый тред (в случае использования так называемой «плоской» модели памяти) может быть оформлен в виде самостоятельного сегмента, что приводит к тому, что простая (не многопоточная) программа будет иметь всего один сегмент кода в виртуальном адресном пространстве.