stringtranslate.com

Поток (вычисления)

Процесс с двумя потоками выполнения, работающий на одном процессоре
Программа , процесс и
планирование потоков , вытеснение , переключение контекста

В информатике поток выполнения — это наименьшая последовательность запрограммированных инструкций, которой может независимо управлять планировщик , который обычно является частью операционной системы . [1] Во многих случаях поток является компонентом процесса .

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

Реализация потоков и процессов различается в разных операционных системах. В книге «Современные операционные системы» Таненбаум показывает , что возможны многие различные модели организации процессов. [2] [ нужна страница ]

История

Потоки впервые появились под названием «задачи» в книге «Мультипрограммирование с переменным числом задач» OS/360 (MVT) в 1967 году. Зальцер (1966) приписывает Виктору А. Высоцкому термин «поток». [3]

Использование потоков в программных приложениях стало более распространенным в начале 2000-х годов, когда процессоры начали использовать несколько ядер. Приложения, желающие использовать преимущества нескольких ядер для повышения производительности, должны были использовать параллелизм для использования нескольких ядер. [4]

Связанные понятия

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

Процессы

На уровне ядра процесс содержит один или несколько потоков ядра , которые совместно используют ресурсы процесса, такие как память и дескрипторы файлов: процесс — это единица ресурсов, а поток — это единица планирования и выполнения. Планирование ядра обычно выполняется упреждающе или, реже, совместно. На уровне пользователя такой процесс, как система выполнения, сам может планировать несколько потоков выполнения. Если они не совместно используют данные, как в Erlang, их обычно аналогично называют процессами [5] , а если они совместно используют данные, их обычно называют (пользовательскими) потоками , особенно если они запланированы с упреждением. Совместно запланированные пользовательские потоки известны как волокна ; разные процессы могут планировать пользовательские потоки по-разному. Пользовательские потоки могут выполняться потоками ядра различными способами (один-к-одному, многие-к-одному, многие-ко-многим). Термин « облегченный процесс » по-разному относится к пользовательским потокам или к механизмам ядра для планирования пользовательских потоков в потоках ядра.

Процесс — это «тяжеловесная» единица планирования ядра, поскольку создание, уничтожение и переключение процессов обходятся относительно дорого . Процессы владеют ресурсами , выделенными операционной системой. Ресурсы включают в себя память (как для кода, так и для данных), дескрипторы файлов , сокеты, дескрипторы устройств, окна и блок управления процессом . Процессы изолируются посредством изоляции процессов и не используют совместное адресное пространство или файловые ресурсы, за исключением явных методов, таких как наследование дескрипторов файлов или сегментов общей памяти или совместное отображение одного и того же файла – см. Межпроцессное взаимодействие . Создание или уничтожение процесса обходится относительно дорого, поскольку необходимо приобретать или высвобождать ресурсы. Процессы обычно являются вытесняющими многозадачными, и переключение процессов является относительно дорогостоящим, помимо базовой стоимости переключения контекста , из-за таких проблем, как очистка кэша (в частности, переключение процессов изменяет адресацию виртуальной памяти, вызывая недействительность и, таким образом, очистку нетегированного резервного буфера трансляции ( TLB), особенно на x86).

Потоки ядра

Поток ядра — это «облегченная» единица планирования ядра. В каждом процессе существует по крайней мере один поток ядра. Если в процессе существует несколько потоков ядра, они используют одну и ту же память и файловые ресурсы. Потоки ядра являются вытесняющими многозадачными, если планировщик процессов операционной системы является вытесняющим. Потоки ядра не владеют ресурсами, за исключением стека , копии регистров , включая программный счетчик , и локальной памяти потока (если таковая имеется), и поэтому их создание и уничтожение относительно дешевы. Переключение потоков также является относительно дешевым: оно требует переключения контекста (сохранение и восстановление регистров и указателя стека), но не меняет виртуальную память и, следовательно, дружественно к кэшу (оставляя TLB действительным). Ядро может назначить один или несколько программных потоков каждому ядру ЦП (оно может назначить себе несколько программных потоков в зависимости от поддержки многопоточности) и может заменять потоки, которые блокируются. Однако обмен потоками ядра занимает гораздо больше времени, чем пользовательские потоки.

Пользовательские темы

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

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

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

Распространенным решением этой проблемы (используемым, в частности, во многих реализациях зеленых потоков) является предоставление API ввода-вывода, который реализует интерфейс, который блокирует вызывающий поток, а не весь процесс, используя внутренний неблокирующий ввод-вывод. и планирование другого пользовательского потока или волокна во время операции ввода-вывода. Аналогичные решения могут быть предоставлены и для других системных вызовов блокировки. Альтернативно, программу можно написать так, чтобы избежать использования синхронного ввода-вывода или других блокирующих системных вызовов (в частности, использования неблокирующего ввода-вывода, включая лямбда-продолжения и/или примитивы async/ await [6] ).

Волокна

Волокна представляют собой еще более легкую единицу планирования, которая планируется совместно : работающее волокно должно явно « уступить », чтобы позволить другому волокну работать, что делает их реализацию намного проще, чем ядра или пользовательские потоки. Файбер можно запланировать для работы в любом потоке одного и того же процесса. Это позволяет приложениям повысить производительность, управляя планированием самостоятельно, вместо того, чтобы полагаться на планировщик ядра (который может быть не настроен для приложения). Среды параллельного программирования, такие как OpenMP, иногда реализуют свои задачи через оптоволокно. [7] [8] Тесно связаны с волокнами сопрограммы , с той разницей, что сопрограммы представляют собой конструкцию уровня языка, а волокна — конструкцию системного уровня.

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

Потоки отличаются от традиционных многозадачных процессов операционной системы по нескольким причинам:

Говорят, что такие системы, как Windows NT и OS/2, имеют дешевые потоки и дорогие процессы; в других операционных системах разница не столь велика, за исключением стоимости переключателя адресного пространства , который на некоторых архитектурах (особенно x86 ) приводит к сбросу буфера резервной трансляции (TLB).

Преимущества и недостатки потоков по сравнению с процессами включают в себя:

Планирование

Упреждающее и кооперативное планирование

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

Одно- и многопроцессорные системы

До начала 2000-х годов большинство настольных компьютеров имели только один одноядерный процессор без поддержки аппаратных потоков , хотя потоки все еще использовались на таких компьютерах, поскольку переключение между потоками обычно происходило быстрее, чем полное переключение контекста процесса . В 2002 году Intel добавила поддержку одновременной многопоточности в процессор Pentium 4 под названием Hyper-Threading ; в 2005 году они представили двухъядерный процессор Pentium D , а AMD представила двухъядерный процессор Athlon 64 X2 .

Системы с одним процессором обычно реализуют многопоточность посредством квантования времени : центральный процессор (ЦП) переключается между различными программными потоками . Такое переключение контекста обычно происходит достаточно часто, чтобы пользователи воспринимали потоки или задачи как выполняемые параллельно (для популярных серверных/настольных операционных систем максимальный интервал времени потока, когда другие потоки ожидают, часто ограничивается 100–200 мс). В многопроцессорной или многоядерной системе несколько потоков могут выполняться параллельно , при этом каждый процессор или ядро ​​одновременно выполняет отдельный поток; на процессоре или ядре с аппаратными потоками отдельные программные потоки также могут выполняться одновременно отдельными аппаратными потоками.

Модели резьбы

1:1 (потоки на уровне ядра)

Потоки, создаваемые пользователем в соответствии 1:1 с планируемыми объектами в ядре [9] , являются простейшей возможной реализацией потоков. OS/2 и Win32 использовали этот подход с самого начала, тогда как в Linux этот подход реализован библиотекой GNU C (через NPTL или более ранние версии LinuxThreads ). Этот подход также используется в Solaris , NetBSD , FreeBSD , macOS и iOS .

M :1 (потоки на уровне пользователя)

Модель M :1 подразумевает, что все потоки уровня приложения сопоставляются с одним запланированным объектом уровня ядра; [9] ядро ​​ничего не знает о потоках приложения. При таком подходе переключение контекста можно осуществить очень быстро и, кроме того, его можно реализовать даже на простых ядрах, не поддерживающих многопоточность. Однако одним из основных недостатков является то, что он не может получить выгоду от аппаратного ускорения на многопоточных процессорах или многопроцессорных компьютерах: одновременно не запланировано более одного потока. [9] Например: если одному из потоков необходимо выполнить запрос ввода-вывода, весь процесс блокируется и преимущество многопоточности не может быть использовано. GNU Portable Threads использует потоки пользовательского уровня, как и State Threads .

M : N (гибридная резьба)

M : N отображает некоторое количество M потоков приложений на некоторое количество N объектов ядра, [9] или «виртуальных процессоров». Это компромисс между потоками уровня ядра («1:1») и уровня пользователя (« N :1»). В общем, системы потоков « M : N » сложнее реализовать, чем потоки ядра или пользовательские потоки, поскольку требуются изменения как в коде ядра, так и в коде пользовательского пространства [ необходимы пояснения ] . В реализации M:N библиотека потоков отвечает за планирование пользовательских потоков в доступных планируемых объектах; это делает переключение контекста потоков очень быстрым, поскольку позволяет избежать системных вызовов. Однако это увеличивает сложность и вероятность инверсии приоритетов , а также неоптимального планирования без обширной (и дорогостоящей) координации между планировщиком пользовательской области и планировщиком ядра.

Примеры гибридной реализации

История моделей потоков в системах Unix

В SunOS 4.x реализованы облегченные процессы или LWP. NetBSD 2.x+ и DragonFly BSD реализуют LWP как потоки ядра (модель 1:1). В версиях от SunOS 5.2 до SunOS 5.8, а также от NetBSD 2 до NetBSD 4 реализована двухуровневая модель, мультиплексирующая один или несколько потоков пользовательского уровня в каждом потоке ядра (модель M:N). SunOS 5.9 и более поздние версии, а также NetBSD 5 устранили поддержку пользовательских потоков и вернулись к модели 1:1. [10] Во FreeBSD 5 реализована модель M:N. FreeBSD 6 поддерживала как 1:1, так и M:N, пользователи могли выбирать, какой из них следует использовать с конкретной программой, используя /etc/libmap.conf. Начиная с FreeBSD 7, соотношение 1:1 стало значением по умолчанию. FreeBSD 8 больше не поддерживает модель M:N.

Однопоточные и многопоточные программы

В компьютерном программировании однопоточная обработка — это обработка одной команды за раз. [11] При формальном анализе семантики переменных и состояния процесса термин « однопоточность» может использоваться по-другому и означать «возврат в пределах одного потока», что распространено в сообществе функционального программирования . [12]

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

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

Потоки и синхронизация данных

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

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

Другие API синхронизации включают переменные условий , критические секции , семафоры и мониторы .

Пулы потоков

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

Многопоточные программы против однопоточных: плюсы и минусы

Многопоточные приложения имеют следующие преимущества перед однопоточными:

Многопоточные приложения имеют следующие недостатки:

Поддержка языков программирования

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

Смотрите также

Рекомендации

  1. ^ Лэмпорт, Лесли (сентябрь 1979 г.). «Как создать многопроцессорный компьютер, который правильно выполняет многопроцессорные программы» (PDF) . Транзакции IEEE на компьютерах . С-28 (9): 690–691. дои : 10.1109/tc.1979.1675439. S2CID  5679366.
  2. ^ Таненбаум, Эндрю С. (1992). Современные операционные системы . Международные издания Прентис-Холла. ISBN 0-13-595752-4.
  3. ^ Зальцер, Джером Ховард (июль 1966 г.). Управление дорожным движением в мультиплексной компьютерной системе (PDF) (докторская диссертация). п. 20.
  4. ^ Саттер, Херб (март 2005 г.). «Бесплатный обед окончен: фундаментальный поворот к параллелизму в программном обеспечении». Журнал доктора Добба . 30 (3).
  5. ^ «Эрланг: 3.1 Процессы».
  6. ^ Игнатченко, Сергей. Восемь способов обработки неблокирующих возвратов в программах передачи сообщений: от C++98 через C++11 до C++20. КППКОН. Архивировано из оригинала 25 ноября 2020 г. Проверено 24 ноября 2020 г.{{cite AV media}}: CS1 maint: bot: исходный статус URL неизвестен ( ссылка )
  7. ^ Ферат, Мануэль; Перейра, Ромен; Руссель, Адриан; Каррибо, Патрик; Стеффенель, Луис-Анджело; Готье, Тьерри (сентябрь 2022 г.). «Улучшение приложений на основе задач MPI + OpenMP для гетерогенных архитектур с поддержкой графических процессоров» (PDF) . OpenMP в современном мире: от поддержки нескольких устройств к метапрограммированию . IWOMP 2022: 18-й международный семинар по OpenMP. Конспекты лекций по информатике. Том. 13527. стр. 3–16. дои : 10.1007/978-3-031-15922-0_1. ISBN 978-3-031-15921-3. S2CID  251692327.
  8. ^ Ивасаки, Синтаро; Амер, Абдельхалим; Таура, Кенджиро; Со, Санмин; Баладжи, Паван. БОЛТ: Оптимизация параллельных областей OpenMP с помощью потоков пользовательского уровня (PDF) . 28-я Международная конференция по параллельным архитектурам и методам компиляции.
  9. ^ abcd Зильбершац, Авраам ; Гэлвин, Питер Баер; Ганье, Грег (2013). Концепции операционной системы (9-е изд.). Хобокен, Нью-Джерси: Уайли. стр. 170–171. ISBN 9781118063330.
  10. ^ «Многопоточность в операционной среде Solaris» (PDF) . 2002. Архивировано из оригинала (PDF) 26 февраля 2009 года.
  11. ^ Менендес, Рауль; Лоу, Дуг (2001). CICS Мураха для программиста COBOL. Майк Мурак и партнеры. п. 512. ИСБН 978-1-890774-09-7.
  12. ^ О'Хирн, Питер Уильям; Теннент, Р.Д. (1997). Алголоподобные языки. Том. 2. Биркхойзер Верлаг . п. 157. ИСБН 978-0-8176-3937-2.
  13. ^ Игнатченко, Сергей (август 2010 г.). «Однопоточность: назад в будущее?». Перегрузка . АККУ (97): 16–19.
  14. ↑ Аб Ли, Эдвард (10 января 2006 г.). «Проблема с потоками». Калифорнийский университет в Беркли.
  15. ^ Игнатченко, Сергей (август 2015 г.). «Многопоточность на уровне бизнес-логики считается вредной». Перегрузка . АККУ (128): 4–7.
  16. Заяц «No Bugs» (12 сентября 2016 г.). «Операционные затраты в тактовых циклах ЦП».

дальнейшее чтение