Потік виконання

Матеріал з Вікіпедії — вільної енциклопедії.
Версія від 19:16, 27 лютого 2020, створена BunykBot (обговорення | внесок) (Категоризація)
Перейти до навігації Перейти до пошуку
Файл:Multithreaded process uk.svg
Процес з двома потоками виконання

Потік (англ. thread) або повніше потік виконання (англ. thread of execution), часто застосовуються назви нитка, нитка виконання та англіцизм тред — в інформатиці так називається спосіб програми розділити себе на дві чи більше паралельні задачі. Реалізація потоків та процесів відрізняються в різних операційних системах, але загалом потік міститься всередині процесу і різні потоки одного процесу спільно розподіляють деякі ресурси, у той час як різні процеси ресурси не розподіляють.

У системах з одним процесором багатопотоковість реалізується загалом поділом часу виконання («кванти часу»), дуже подібно до паралельного виконання багатьох задач: процесор послідовно переключається між різними потоками. Це переключення контексту відбувається настільки швидко, що в кінцевого користувача створюється ілюзія одночасного виконання. На багатопроцесорних чи на багатоядерних системах робота потоків здійснюється справді одночасно, оскільки різні потоки і процеси виконуються буквально одночасно різними процесорами або ядрами процесора. Особливості квантування в операційній системі Windows розглянуто в статті Квант (Windows).

Багато сучасних операційних систем прямо підтримують квантування часу і багатопроцесорну роботу потоків через планувальник процесів. Ядро операційної системи дозволяє програмісту маніпулювати потоками через інтерфейс системних викликів. Деякі реалізації викликають потоки ядра, оскільки легковагові процеси (англ. lightweight process, LWP) є спеціальним типом потоків ядра, що розподіляють деякі стани та інформацію.

Поза тим, програма може емулювати роботу потоків, використовуючи таймер, сигнали або інші методи, щоб перервати власне виконання і послідовно виконувати різні задачі власним квантуванням часу. Такий спосіб іноді зветься потоками користувацького простору (англ. user-space threads) або волокнами.

Зауваження щодо термінології

«Англо-український тлумачний словник з обчислювальної техніки, інтернету і програмування» (Київ, видавництво «СофтПрес», 2005), виданий за підтримки компанії Microsoft, подає переклад оригінального терміну thread українською мовою як потік, нитка, тред (ст. 494).

Порівняння потоків з процесами

Потоки відрізняються від традиційних процесів багатозадачних операційних систем, в тому що процеси:

Потоки всередині процесу, з іншої сторони, розподіляють інформацію про стан процесу, і прямо доступаються до спільної пам'яті та інших ресурсів. Переключення контексту між потоками процесу на загал швидше, ніж переключення контексту між процесами. Описуючи ситуацію такі системи, як Windows NT та OS/2, кажуть, що мають «дешеві» потоки та «дорогі» процеси; в інших операційних системах ситуація не дуже відмінна.

Потоки вимагають більшої кваліфікації програмістів

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

Ця перевага багатопотокових програм дозволяє їм скоріше виконуватися на комп'ютерах з кількома процесорами, процесорами з кількома ядрами, або на кластері машин, бо потоки програми природно надають собі можливість справді паралельного виконання. У такому випадку програміст має бути особливо обережним, щоб уникнути стану гонитви та інших непередбачених поведінок. Для забезпечення коректної роботи з даними, потоки часто потребують процедури доступу до даних в певному порядку. Потоки також вимагають атомарних операцій (часто реалізованих з використанням семафорів), щоб уникнути одночасної неузгодженої модифікації даних або їхнього читання, у той час як процес модифікує дані. Недбале поводження з такими примітивами може призвести до взаємного блокування.

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

Традиційно базове комп'ютерне апаратне забезпечення не надавало багато підтримки багатонитевості, оскільки переключення між нитями загалом є вже скорішою процедурою порівняно з переключенням контексту процесів. Процесори вбудованих систем, що мають більші вимоги до поведінки в реальному часі, можуть мати апаратну підтримку багатонитевості зменшенням часу перемикання, можливо розміщуючи виділений блок регістрів для кожної ниті, замість того, щоб зберігати/читати загальні регістри. На кінці 1990-х стала відома ідея виконання інструкцій з різних нитей одночасно, це зветься одночасною багатонитевостю. Ця можливість була введена в процесорі Intel Pentium 4 під ім'ям Hyper-threading.

Процеси, нитки і волокна

Процес є «найважчим» об'єктом для планувальника ядра операційної системи. Власні ресурси процесу розміщені операційною системою. Ресурси включають пам'ять, відкриті файли та пристрої, сокети та вікна. Процеси не розділяють з кимось адресний простір чи залучені файли, за винятком явних методів, таких як успадкування файлів чи сегментів спільної пам'яті, або робота з файлами в режимі спільного доступу. Типово, процеси запобіжно багатозадачні. Але Windows 3.1 і старі версії Mac OS використовували кооперативну багатозадачність.

Нитка є «найлегшим» об'єктом для планувальника ядра. Щонайменше одна нитка існує всередині кожного процесу. Якщо в процесі співіснують багато ниток, вони розподіляють одну пам'ять і файлові ресурси. Нитки мають запобіжну багатозадачність, якщо планувальник процесів операційної системи запобіжний. Нитки не мають своїх власних ресурсів, за винятком стеку, копії регістрів включно з лічильником задач, і власне-нитковим зберіганням даних (якщо така можливість забезпечується).

В деяких ситуаціях є різниця між «нитками ядра» та «нитками користувача» — перші управляються і плануються ядром, другі управляються і плануються в просторі користувача. В цій статті термін «нитка» використовується для позначення поняття «нитка ядра», а поняття «волокно» (англ. fiber) означає нитка користувача. Волокна мають кооперативне планування: працююче волокно має явно зупинитися і передати управління іншому волокну. Волокно може бути запущено будь-якою ниткою в одному процесі.

Проблеми нитей та волокон

Паралелізм та структури даних

Нитки одного процесу розподіляють один адресний простір. Це дозволяє паралельно працюючому коду бути тісно взаємопов'язаним і зручно обмінюватися даними без залучення складних методів міжпроцесорного обміну. Але, доступні всім ниткам, навіть прості структури даних стають вразливі до помилок типу стану гонитви, якщо вони потребують більше однієї машинної команди під час присвоєння нових значень: дві нитки можуть одночасно спробувати змінити значення структури даних і внаслідок отримати зовсім не те, що бажалося. Такі помилки буває дуже важко виправити і ізолювати.

Щоб запобігти цьому, програмний інтерфейс ниток пропонує методи синхронізації, такі як м'ютекс (mutex) для блокування структур даних під час доступу при паралельному виконанні. На однопроцесорних системах, нитка, що запитала закритий м'ютекс, має перейти в пасивний (сонний) режим і планувальник переключає контекст іншої нитки. В багатопроцесорних системах, нитка (за відсутності інших конкуруючих ниток виконання на процесорі) стане запитувати м'ютекс, доки той не звільниться. Обидва ці способи зле впливають на продуктивність, а в випадку мультипроцесорності виникає навантаження на шину пам'яті, особливо якщо деталізація блокування висока.

Введення-виведення і планування

Багато реалізацій волокон лежать повністю в області користувача. Як наслідок, переключення контексту між волокнами всередині процесу, є максимально ефективним, бо воно не вимагає ніякої взаємодії з ядром: переключення контексту може бути зроблене локальним збереженням регістрів процесора, що використані у волокні, і завантаженням регістрів наступного волокна, що виконуватиметься. Оскільки планування відбувається в області користувача, політика планування може бути простіше скроєна найефективнішим чином для швидкодії програми загалом.

Проте, використання системних викликів блокування у волокнах може бути проблематичним. Якщо волокно виконує системний виклик блокування, інші волокна процесу не можуть виконуватися, доки системний виклик не відпрацює. Типовим прикладом цієї проблеми є виконання введення-виведення: більшість програм пише у вивід синхронно. Коли операція вводу-виводу розпочалася, системний виклик зроблено, і він не поверне керування, доки операція вводу-виводу не буде завершена. На весь цей час робота всього процесу блокована ядром, процес «стоїть», бо інші волокна процесу не можуть отримати керування.

Загальним рішенням цієї проблеми є забезпечення програмного синхронного інтерфейсу з використанням внутрішнього неблокуючого введення-виведення, так, щоб під час виконання запису-читання інші волокна могли виконуватися. Таке рішення може бути запроваджене для інших блокуючих викликів. Інший спосіб — писати програму без використання синхронного введення-виведення та інших блокуючих системних викликів.

Win32 забезпечує програмний інтерфейс волокон[1]. SunOS 4.x реалізує волокна під назвою «легковагові процеси» (англ. light-weight processes, LWP), відомі також як «зелені нитки». NetBSD 2.x+, і DragonFly BSD також реалізають LWP як нитки (модель 1:1). SunOS від версій 5.2 до 5.8, NetBSD версій від 2 до 4 реалізує дворівневу модель, дозволяючи одному чи більше користувацьких волокон на кожну нитку ядра (модель M: N). SunOS 5.9 і пізніші, а також NetBSD 5 позбавлені підтримки волокон, повернулися до моделі 1:1.[2]

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

Моделі

1:1

Користувацькі нитки моделі 1:1 відповідають одна до одного планувальному об'єктові ядра. Це найпростіша можлива реалізація ниток. В Linux цей підхід реалізує звична бібліотека C (NPTL).

N: M

N: M розкладає N ниток застосунку на M об'єктів ядра, або «віртуальних процесорів». Така реалізація є компромісом між реалізацією ниток рівня ядра («1:1») і рівнем користувача («N:1»). Загалом, ниткова система «N: M» складніша в реалізації і за ядерну, і за користувацьку системи, бо потрібні зміни і в ядрі і з боку користувача. В реалізації m×n, бібліотека ниток відповідальна за планування ниток користувача на можливі об'єкти планування; це робить переключення контексту ниток дуже швидким, бо не потрібні системні виклики. Проте, це збільшує складність і ймовірність інверсії пріоритетів, а також вимагає екстенсивної (і дорогої) координації між планувальниками ядра та користувача.

N:1

Модель N:1 означає відобразити всі нитки застосунку на єдиний планувальний об'єкт ядра; оскільки ядро нічого не знає про користувацькі нитки, фактично це є реалізація волокон. При такому підході переключення контексту може бути зроблене дуже швидко і, на додаток, воно може бути реалізоване на простому ядрі без підтримки ниток. Один з найбільших недоліків, це те, що неможливо отримати користь від апаратного пришвидшення на багатоядерних процесорах або багатопроцесорних комп'ютерах: у цій моделі неможливо запустити більше однієї нитки одночасно.

Реалізації

Існує багато різних (і несумісних) реалізацій ниток, включаючи реалізації на рівні ядра і на рівні користувача.

Звісно, волокна можуть бути реалізовані поза підтримкою з боку операційної системи, хоча деякі операційні системи або бібліотеки забезпечують їхню явну підтримку. Наприклад, Microsoft Windows (Windows NT 3.51 SP3 і пізніші) підтримують програмний інтерфейс волокон для застосунків, тож за бажання, можна спробувати підвищити продуктивність програми самому, замість покладатися на планувальник ядра (який може бути не налаштований для застосунку). Прикладом може бути планувальник користувацького режиму в Microsoft SQL Server 2000, який працює в просторі волокон.

Приклади реалізацій рівня ядра

Приклади реалізації рівня користувача

Приклади гібридних реалізацій

  • Scheduler activations використовується бібліотекою рідних-POSIX ниток NetBSD (модель N: M на противагу моделі ядра 1:1 та моделі користувацького простору)
  • Marcel з проекту PM2.

Реалізація в Windows

Докладніше: Нитки у Windows

Visual C/C++ реалізує інтерфейс ниток двома способами

  • через функція CreateThread і споріднені (варіант: AfxBeginThread і споріднені)
  • через функції _beginthread, _beginthreadex і споріднені

Перша реалізація є дефектною і призводить до витоку пам'яті при використанні в обробниках ниток старих функцій стандартної бібліотеки.[3]

Існують реалізації стандарту POSIX pthread для Windows[4] як надбудова над інтерфейсом _beginthread.

Виноски

Див. також

Посилання