Потоки у Windows

Матеріал з Вікіпедії — вільної енциклопедії.
(Перенаправлено з Ниті у Windows)
Jump to navigation Jump to search

Потік у Windows — реалізація потоків в родині операційних систем Windows, структури всередині процесу, які містять виконуваний код та отримують процесорний час для його виконання з метою одночасного чи псевдо-одночасного виконання кількох задач[1]. Новостворений процес має один потік виконання, і під час роботи може породжувати інші потоки (і зупиняти їх)[2]. Алгоритми, які реалізується у кількох потоках, називають багатопотоковими.

Переваги багатопотоковості[ред.ред. код]

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

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

Таким чином, доцільне використання потоків може значно поліпшити продуктивність і зручність використання програм.

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

Загальна характеристика потоків[ред.ред. код]

Хоча в кожного потоку є свій контекст виконання. Кожний потік усередині одного процесу використовує його віртуальний адресний простір (а також інші ресурси, які належать процесу). Це означає, що всі потоки в процесі можуть записувати й зчитувати вміст пам'яті інших потоків цього процесу. Але потоки не можуть посилатися на адресний простір іншого процесу. Виняток може бути в ситуації, коли процес надає частину свого адресного простору як розділ загальної пам'яті через об'єкт «проектований файл» (file mapping object), або коли один із процесів має право на відкриття іншого процесу й використовує функції доступу до пам'яті між процесами.

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

Потік містить такі важливі елементи:

  • завантажений для виконання код;
  • вміст набору регістрів процесора, що відображають стан процесора;
  • два стеки, один із яких використовується потоком при виконанні в режимі ядра, а інший — у користувацькому режимі;
  • закриту область пам'яті, яку називають локальною пам'яттю потоку (thread-local storage, TLS); вона використовується підсистемами, бібліотеками виконуваних систем (run-time libraries) і DLL;
  • унікальний ідентифікатор потоку;
  • іноді потоки мають свій контекст захисту, який використовується багатопотоковими серверними програмами, що підміняють контекст захисту клієнтів.

Контекст потоку[ред.ред. код]

Вміст змінних регістрів, стеку й локальних областей пам'яті називають контекстом потоку. Оскільки ця інформація відмінна для кожної апаратної платформи, на якій може працювати Windows, відповідна структура даних також є платформо-залежною. Функція GetThreadContext надає доступ до цієї апаратно-залежної інформації (яку називають блоком CONTEXT).

Рівні пріоритету потоків[ред.ред. код]

Рівні пріоритету потоків

У Windows передбачено 32 рівні пріоритету — від 0 до 31. Ці значення групуються так:

  • шістнадцять рівнів реального часу (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[3]. Windows API спочатку впорядковує процеси за класами пріоритету, призначеними при їхньому створенні (Realtime (реального часу), High (високий), Above Normal (вище звичайного), Normal (звичайний), Below Normal (нижче звичайного) і Idle (простоюючий)), а потім — за відносним пріоритетом індивідуальних потоків у межах цих процесів (Time-critical (критичний за часом), Highest (найвищий), Above Normal (вище звичайного), Normal (звичайний), Below Normal (нижче звичайного), Lowest (найменший) і Idle (простоюючий)).

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

Стани потоків[ред.ред. код]

Стани потоків у Windows та можливі переходи між станами

потік може бути в таких станах[1]:

  • Initialized (ініціалізований). У цей стан потік входить у процесі свого створення.
  • Ready (готовий). Потік у стані готовності очікує виконання. Вибираючи наступний потік для виконання, диспетчер бере до уваги тільки пул потоків, готових до виконання.
  • Standby (простоює). Потік у цьому стані вже вибраний наступним для виконання на конкретному процесорі. У підходящий момент диспетчер перемикає контекст на цей потік. У стані Standby може перебувати тільки один потік для кожного процесора в системі. Проте потік може бути витиснений навіть у цьому стані (якщо, наприклад, до початку виконання такого потоку до виконання буде готовий потік з вищим пріоритетом).
  • Running (виконується). Потік переходить у цей стан і починає виконуватись відразу після того, як диспетчер перемикає на неї контекст. Виконання потоку припиняється, коли він завершується, витискується потоком з вищим пріоритетом, перемикає контекст на інший потік, самостійно переходить у стан очікування або минає виділений йому квант процесорного часу (та інший потік з тим же пріоритетом готова до виконання).
  • Waiting (очікує). Потік входить у цей стан кількома способами. Він може самостійно почати очікування на синхронізуючому об'єкті або його змушує до цього підсистема оточення. Після закінчення очікування  — залежно від пріоритету — або негайно починає виконуватися, або переходить у стан Ready.
  • Transition (перехідний стан). Потік переходить у цей стан, якщо він готовий до виконання, але його стек ядра вивантажений з пам'яті. Щойно цей стек завантажується у пам’ять, потік переходить у стан Ready.
  • Terminated (завершений). Закінчуючи виконання, потік переходить у стан Terminated. Після цього інформація про потік у виконавчій системі може бути знищена.

Можливі переходи між станами потоків показано на діаграмі.

Планування потоків у Windows[ред.ред. код]

Детальніше: Планування потоків

Системний алгоритм керування послідовністю виконання потоків та розподілу процесорного часу між ними називають плануванням потоків. У Windows реалізовано підсистему витискуючого планування потоків на основі рівнів пріоритету, у якій завжди виконується готовий до виконання потік з найбільшим пріоритетом[3]. Але вибір потоку для виконання може бути обмежений набором процесорів, на яких вона може працювати. Це явище називають прив'язкою до процесорів (processor affinity). За замовчуванням потік виконується на будь-якому доступному процесорі, але можна змінити прив'язку до процесорів через функції планування.

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

Код Windows, що відповідає за планування, реалізований у ядрі. Оскільки цей код розосереджений по ядру, єдиного модуля або процедури з назвою "планувальник" немає. Сукупність процедур, що виконують ці обов'язки, називають диспетчером ядра (kernel's dispatcher). Диспетчеризація потоків може бути викликана кожною з наступних подій:

  • потік готовий до виконання — наприклад, він щойно створений або вийшов зі стану очікування.
  • потік виходить зі стану Running (виконується), оскільки її квант минув, або потік завершується, або переходить у стан очікування.
  • пріоритет потоку змінюється в результаті виклику системного сервісу або самої Windows.
  • змінюється прив'язка до процесорів, через що потік більше не може працювати на процесорі, на якому він виконувався.

У будь-якому випадку Windows повинна визначити, який потік виконувати наступним. Вибравши новий потік, Windows перемикає контекст. Ця операція полягає в збереженні параметрів стану машини, пов'язаних з поточним потоком, і завантаженні аналогічних параметрів для іншого потоку, після чого починається виконання нового потоку.

Планування у Windows здійснюється на рівні потоків. Оскільки рішення, прийняті в ході планування, стосуються винятково потоками, система не звертає уваги на те, до якого процесу належить потік. Якщо в процесу А є 10, у процесу В — 2 готові до виконання потоку, і всі 12 мають однаковий пріоритет, кожен з потоків теоретично отримає 1/12 процесорного часу, тому що Windows не стане порівну ділити процесорний час між двома процесами (вважаємо, що класи пріоритету процесів також однакові).

Щоб зрозуміти алгоритми планування потоків, слід розглянути рівні пріоритету Windows.

Потік простою (Idle)[ред.ред. код]

Якщо немає жодного потоку, готового до виконання на процесорі, Windows під'єднує до даного процесора системний потік простою (процесу Idle). Для кожного процесора створюється свій потік простою.

Різні утиліти для перегляду процесів у Windows по-різному називають процес Idle. В нього немає рівня пріоритету, оскільки він виконується лише за відсутності інших потоків. Хоча послідовність роботи потоку простою залежить від архітектури, вона завжди виконує такі дії:

  1. Вмикає та вимикає переривання.
  2. Перевіряє, чи обраний якісь потік для виконання на даному процесорі, і якщо так, організовує його диспетчеризацію.
  3. Викликає з HAL процедуру обробки процесора в простої (якщо потрібно виконати функції керування електроживленням).

У Windows Server потік простою також перевіряє наявність потоків, що очікують виконання на інших процесорах.

Створення потоку[ред.ред. код]

Життєвий цикл потоку починається при його створенні програмою. Запит на створення потоку надходить виконавчій системі Windows, диспетчер процесів виділяє пам'ять для об'єкта "потік" і викликає ядро для ініціалізації блоку потоку. Нижче перераховані основні етапи створення потоку функцією API CreateThread (вона міститься в Kernel32.dll)[4]:

  1. Створення стеку користувацького режиму в адресному просторі процесу.
  2. Ініціалізація апаратного контексту потоку, специфічного для конкретної архітектури процесора.
  3. Для створення об'єкта «потік» виконавчої системи викликається NtCreateThread. CreateThread повідомляє підсистему Windows про створення нового потоку і та виконує підготовчі операції.
  4. Викликаючому коду повертаються дескриптор і ідентифікатор потоку, згенерований на попередньому етапі.
  5. Виконання потоку відновлюється і йому може бути виділено процесорний час (якщо він не був створений із прапорцем 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, що перетворить потік у волокно. Отримане волокно може створювати додаткові волокна (у кожного волокна може бути свій набір волокон). Виконання волокна (на відміну від потоку) не починається доти, поки воно не буде ініційоване вручну. Волокно працює до завершення або до перемикання на інше волокно.

Джерела[ред.ред. код]

  1. а б в г Коноваленко І.В., Федорів П.С. Системне програмування у Windows з прикладами на Delphi, Т:ТНТУ.- 2012.
  2. MSDN. Processes and Threads(англ.)
  3. а б Руссинович М. Внутреннее устройство Microsoft Windows : Windows Server 2003, Windows XP и Windows 2000. Мастер-класс / М.Руссинович, Д.Соломон ; пер. с англ. – 4-е изд. – М : Издательско-торговый дом "Русская редакция" ; СПб : Питер, 2005.
  4. Рихтер Дж. Windows для профессионалов: создание эффективных Win32 приложений с учетом специфики 64-разрядной версии Windows/ Дж.Рихтер ; пер. с англ. – 4-е изд. – СПб. : Питер; М.: Издательско-торговый дом "Русская редакция", 2001.

Дивись також[ред.ред. код]