Потоки у Windows
Потік у Windows — реалізація потоків в родині операційних систем Windows, структури всередині процесу, які містять виконуваний код та отримують процесорний час для його виконання з метою одночасного чи псевдо-одночасного виконання кількох задач[1]. Новостворений процес має один потік виконання, і під час роботи може породжувати інші потоки (і зупиняти їх)[2]. Алгоритми, які реалізується у кількох потоках, називають багатопотоковими.
Переваги багатопотоковості
Багатопотоковість програми створює підґрунтя для реалізації реальної багатозадачності — виконання кількох завдань одночасно (якщо обчислювальна система є багатопроцесорною), або «псевдоодночасно» («майже одночасно») на однопроцесорних системах. Наявність кількох потоків дозволяє:
- Оптимізувати організацію поведінки програми. Часто поведінка програми може бути організована у кілька незалежних паралельних алгоритмів, тоді їх можна винести в окремі потоку. При цьому їм можна задавати різний пріоритет виконання.
- Обходити критичні до часу операції. Якщо програма має лише один потік, то вона повинна зупинити все виконання при очікуванні повільних операцій, таких як запис у файл чи відображення засобами мультимедіа. При цьому процесор перебуває у простої, поки ця операція не завершиться. Якщо застосунок складається з кількох потоків, він може продовжувати виконання в окремих потоках, коли один потік очікуватиме на завершення повільної операції.
- Реалізувати багатопроцесорну обробку. Якщо система, у якій працює програма, є мультипроцесорною, то можна сповна скористатись наявними обчислювальними ресурсами і підвищити її ефективність шляхом використанням кількох потоків. При цьому різні потоки можуть виконуватись одночасно на різних процесорах.
Таким чином, доцільне використання потоків може значно поліпшити продуктивність і зручність використання програм.
На одному процесорі багатопотоковість відбувається шляхом тимчасової активізації різних потоків. Таке перемикання відбувається досить часто, щоб користувач сприймав виконання потоків як одночасне. У багатопроцесорних і багатоядерних системах потоки можуть реально виконуватись одночасно. При цьому кожен процесор або ядро обробляє окремий потік (або кілька потоків).
Загальна характеристика потоків
Хоча в кожного потоку є свій контекст виконання. Кожний потік усередині одного процесу використовує його віртуальний адресний простір (а також інші ресурси, які належать процесу). Це означає, що всі потоки в процесі можуть записувати й зчитувати вміст пам'яті інших потоків цього процесу. Але потоки не можуть посилатися на адресний простір іншого процесу. Виняток може бути в ситуації, коли процес надає частину свого адресного простору як розділ загальної пам'яті через об'єкт «проектований файл» (file mapping object), або коли один із процесів має право на відкриття іншого процесу й використовує функції доступу до пам'яті між процесами.
За замовчуванням у потоків немає власного маркера доступу, але він може отримати його і це дозволить йому підміняти контекст захисту іншого процесу.
Потік містить такі важливі елементи:
- завантажений для виконання код;
- вміст набору регістрів процесора, що відображають стан процесора;
- два стеки, один із яких використовується потоком при виконанні в режимі ядра, а інший — у користувацькому режимі;
- закриту область пам'яті, яку називають локальною пам'яттю потоку (thread-local storage, TLS); вона використовується підсистемами, бібліотеками виконуваних систем (run-time libraries) і DLL;
- унікальний ідентифікатор потоку;
- іноді потоки мають свій контекст захисту, який використовується багатопотоковими серверними програмами, що підміняють контекст захисту клієнтів.
Контекст потоку
Вміст змінних регістрів, стеку й локальних областей пам'яті називають контекстом потоку. Оскільки ця інформація відмінна для кожної апаратної платформи, на якій може працювати Windows, відповідна структура даних також є платформо-залежною. Функція GetThreadContext надає доступ до цієї апаратно-залежної інформації (яку називають блоком CONTEXT).
Рівні пріоритету потоків
Windows має 32 рівня пріоритету — від 0 до 31. Вони групуються як:[3]
- шістнадцять рівнів реального часу (16-31);
- п'ятнадцять динамічних рівнів ( 1-15);
- один системний рівень (0), зарезервований для потоку обнуління сторінок пам'яті (zero page thread).
У межах кожного класу пріоритету можуть бути наступні рівні пріоритету потоків[1]:
- THREAD_PRIORITY_IDLE,
- THREAD_PRIORITY_LOWEST,
- THREAD_PRIORITY_BELOW_NORMAL,
- THREAD_PRIORITY_NORMAL,
- THREAD_PRIORITY_ABOVE_NORMAL,
- THREAD_PRIORITY_HIGHEST,
- THREAD_PRIORITY_TIME_CRITICAL.
За замовчуванням потоки створюються із значенням пріоритету THREAD_PRIORITY_NORMAL. Значення рівнів пріоритету потоків THREAD_PRIORITY_ABOVE_NORMAL та THREAD_PRIORITY_HIGHEST найчастіше використовуються у потоках, що призначені для взаємозв'язку з користувачами. Фонові потоки, які не потребують значних процесорних ресурсів, можуть мати пріоритет THREAD_PRIORITY_BELOW_NORMAL або THREAD_PRIORITY_LOWEST.
Рівні пріоритету потоку призначаються з урахуванням двох різних точок зору — Windows API і ядра Windows[4]. Windows API спочатку впорядковує процеси за класами пріоритету, призначеними при їхньому створенні (Realtime (реального часу), High (високий), Above Normal (вище звичайного), Normal (звичайний), Below Normal (нижче звичайного) і Idle (простоюючий)), а потім — за відносним пріоритетом індивідуальних потоків у межах цих процесів (Time-critical (критичний за часом), Highest (найвищий), Above Normal (вище звичайного), Normal (звичайний), Below Normal (нижче звичайного), Lowest (найменший) і Idle (простоюючий)).
Базовий пріоритет кожного потоку у Windows API встановлюється, виходячи із класу пріоритету її процесу й відносного пріоритету самого потоку.
Стани потоків
Стани потоків:[1]
- Initialized (ініціалізований)
- Ready (готовий)
- Standby (простоює)
- Running (виконується)
- Waiting (очікує)
- Transition (перехідний стан)
- Terminated (завершений)
Планування потоків у Windows
Детальніше: Планування потоків
Системний алгоритм керування послідовністю виконання потоків та розподілу процесорного часу між ними називають плануванням потоків. У Windows реалізовано підсистему витискуючого планування потоків на основі рівнів пріоритету, у якій завжди виконується готовий до виконання потік з найбільшим пріоритетом[4]. Але вибір потоку для виконання може бути обмежений набором процесорів, на яких вона може працювати. Це явище називають прив'язкою до процесорів (processor affinity). За замовчуванням потік виконується на будь-якому доступному процесорі, але можна змінити прив'язку до процесорів через функції планування.
Створення потоку
Життєвий цикл потоку починається при його створенні програмою. Запит на створення потоку надходить виконавчій системі Windows, диспетчер процесів виділяє пам'ять для об'єкта «потік» і викликає ядро для ініціалізації блоку потоку. Нижче перераховані основні етапи створення потоку функцією API CreateThread (вона міститься в Kernel32.dll)[5]:
- Створення стеку користувацького режиму в адресному просторі процесу.
- Ініціалізація апаратного контексту потоку, специфічного для конкретної архітектури процесора.
- Для створення об'єкта «потік» виконавчої системи викликається NtCreateThread. CreateThread повідомляє підсистему Windows про створення нового потоку і та виконує підготовчі операції.
- Викликаючому коду повертаються дескриптор і ідентифікатор потоку, згенерований на попередньому етапі.
- Виконання потоку відновлюється і йому може бути виділено процесорний час (якщо він не був створений із прапорцем CREATE_SUSPENDED).
Функція CreateThread оголошена так (визначення за допомогою мови Delphi)[1]:
function CreateThread( lpThreadAttributes: Pointer; // Атрибути безпеки потоку dwStackSize: DWORD; // Розмір стеку для потоку lpStartAddress: TFNThreadStartRoutine; // Функція потоку lpParameter: Pointer; // Аргумент для нового потоку dwCreationFlags: DWORD; // Прапорці створення var lpThreadId: DWORD // Ідентифікатор потоку ): THandle; // Дескриптор потоку
Ключовим параметром цієї функції є вказівник lpStartAddress, який вказує на функцію потоку. Саме у ній міститься виконуваний код потоку. Як правило, це адреса функції, яка приймає єдиний 32-бітний вказівник у ролі аргументу та повертає 32-бітний вихідний код.
Конкуренція (конфлікт) потоків
Детальніше: Конкуренція потоків
У зв'язку з тим, що Windows використовує витискуючу багатозадачність, при якій виконання будь-якої потоку може бути перерване в будь-який момент часу, доступ кількох потоків до однієї ділянки пам'яті процесу може спричинити суттєві проблеми. Справа у тому, що багато операцій при виконанні програми повинні бути атомарними, тобто неподільними: коли потік виконує таку дію, інші потоку бачать її або ще непочатою, або вже завершеною. Якщо потоку не синхронізовані, то всі операції аж до рівня процесорних інструкцій є неатомарні. Ситуація, при якій різні потоки, виконуючи доступ до спільного для них ресурсу, порушують логіку алгоритму програми, називають конкуренцією (конфліктом) потоків.
Для запобігання помилкам, які можуть виникати при узгодженні роботи кількох потоків у Windows, використовують засоби синхронізації: критичні секції, м'ютекси, семафори та події. Об'єкти синхронізації забезпечують доступ до системних ресурсів, які можуть перебувати під керуванням одного чи кількох процесів. Об'єкт синхронізації може перебувати у двох станах: сигнальному та несигнальному. Коли об'єкт перебуває у стані зайнятості, або несигнальному стані, очікуюча потік не може працювати. Сигнальний стан об'єкта синхронізації дозволяє продовжити роботу потоку.
Волокна
Система Windows забезпечує також функціонування механізму волокон (fibers). Волокна дозволяють програмам створювати власні «потоку виконання», не використовуючи системний механізм планування потоків на основі пріоритетів. Волокна часто називають «полегшеними» потоками. Вони невидимі ядру, оскільки реалізуються у користувацькому режимі. Для створення волокна потрібно викликати функцію ConvertThreadToFiber, що перетворить потік у волокно. Отримане волокно може створювати додаткові волокна (у кожного волокна може бути свій набір волокон). Виконання волокна (на відміну від потоку) не починається доти, поки воно не буде ініційоване вручну. Волокно працює до завершення або до перемикання на інше волокно.
Джерела
- Коноваленко І. В., Федорів П. С. Системне програмування у Windows з прикладами на Delphi, Т:ТНТУ.- 2012.
- MSDN. Processes and Threads(англ.)
- Руссинович М. и Соломон Д. (2008). Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP и Windows 2000. Мастер-класс. Москва: Издательзство «Русская Редакция». с. 346. ISBN 978-5-459-01730-4.
- Руссинович М. Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP и Windows 2000. Мастер-класс / М.Руссинович, Д.Соломон ; пер. с англ. — 4-е изд. — М: Издательско-торговый дом «Русская редакция» ; СПб: Питер, 2005.
- Рихтер Дж. Windows для профессионалов: создание эффективных Win32 приложений с учетом специфики 64-разрядной версии Windows/ Дж. Рихтер ; пер. с англ. — 4-е изд. — СПб. : Питер; М.: Издательско-торговый дом «Русская редакция», 2001.