- •4.6.2. Програмний інтерфейс планування
- •4.1. Загальні принципи планування
- •4.1.1. Особливості виконання потоків
- •4.1.2. Механізми і політика планування
- •4.1.3. Застосовність принципів планування
- •4.2. Види планування
- •4.2.1. Довготермінове планування
- •4.3. Стратеги планування. Витісняльна і невитісняльна багатозадачність
- •4.2.2. Середньотермінове планування
- •4.2.3. Короткотермінове планування
- •4.4. Алгоритми планування
- •4.4.1. Планування за принципом fifo
- •4.4.2. Кругове планування
- •4.4.4. Планування на підставі характеристик подальшого виконання
- •4.4.5. Багаторівневі черги зі зворотним зв’язком
- •4.4.6. Лотерейне планування
- •4.5. Реалізація планування в Linux
- •4.5.1. Планування процесів реального часу в ядрі
- •4.5.2. Традиційний алгоритм планування
- •4.5.3. Сучасні підходи до реалізації планування
- •4.5.4. Програмний інтерфейс планування
- •4.6. Реалізація планування у Windows хр
- •4.6.1. Планування потоків у ядрі
- •4.6.2. Програмний інтерфейс планування
- •Висновки
- •5.1. Основні принципи взаємодії потоків
- •5.2. Основні проблеми взаємодії потоків
- •5.2.1. Проблема змагання
- •5.2.2. Критичні секції та блокування Поняття критичної секції
- •5.3. Базові механізми синхронізації потоків
- •5.3.1. Семафори
- •5.3.2. М’ютекси
- •5.3.3. Умовні змінні та концепція монітора
- •5.3.4. Блокування читання-записування
- •5.3.5. Синхронізація за принципом бар’єра
- •5.4. Взаємодія потоків у Linux
- •5.4.1. Механізми синхронізації ядра Linux
- •5.4.2. Синхронізація процесів користувача у Linux. Ф’ютекси
- •5.5. Взаємодія потоків у Windows хр
- •5.5.1. Механізми синхронізації потоків ос
- •5.5.2. Програмний інтерфейс взаємодії Win32 арі
- •Висновки
- •Контрольні запитання та завдання
5.3.5. Синхронізація за принципом бар’єра
Іноді буває зручно розбити задачу (наприклад, складний розрахунок) на кілька послідовних етапів, при цьому виконання наступного етапу не може розпочатися без цілковитого завершення попереднього. Якщо на кожному етапі роботу розподілити по окремих потоках, які паралельно виконуватимуть кожен свою частину, виникає запитання: яким чином дочекатися завершення всіх потоків попереднього етапу до початку наступного?
Звичайне використання thread_join( ) тут не підходить, тому що приєднати можна якийсь конкретний потік, а не цілий набір потоків. Для розв’язання цього завдання запропоновано концепцію спеціального синхронізаційного об’єкта - бар’єра.
Бар’єр — цс блокування, яке зберігають доти, поки кількість потоків, що очікують, не досягне деякого наперед заданого чиста, після чого всі ці потоки продовжують своє виконання.
Так, якщо наприкінці етапу задачі поставити бар’єр, він буде утримувати потоки від продовження, поки вони всі не завершать роботу цього етапу.
Операції бар’єра
Розглянемо дві операцій бар’єра.
♦ Ініціалізація бар’єра (barrier_init). Параметром даної операції є n — кількість потоків, які ми синхронізуватимемо на цьому бар’єрі. Внутрішній лічильник призупинених потоків покладають рівним нулю. Значення n після, виклику barrier_init міняти не можна.
♦ Очікування на бар’єрі (barri erwai t). Внаслідок даної операції лічильник призупинених потоків збільшується на одиницю. Якщо він не досягнув п, цей потік призупиняють (кажуть, що він «очікує на бар’єрі»). Якщо ж після збільшення лічильник виявляється рівним п (досягнуто максимальної кількості потоків для бар’єра), усі потоки, що очікували на бар’єрі, поновлюються (ситуація переходу бар’єра), при цьому одному з них повертають спеціальне значення (зазвичай -1), а іншим — нуль. Надалі потік, що отримав це спеціальне значення, може зробити деякі підсумкові дії після переходу бар’єра. При переході бар’єра його «скидають» (внутрішній лічильник призупинених потоків знову покладають рівним 0) і він починаючи із наступної операції barrier_wsit блокуватиме потоки знову.
Ось псевдокод прикладу використання бар’єра:
Тут виконання функції потоку розбито на етапи. Кожен етап не може бути виконаний без цілковитого завершення попереднього. Створюють п потоків, кожен з яких приходить до бар’єра наприкінці етапу. Після переходу бар’єра потоки починають виконувати дії наступного етапу, в кінці якого стоїть новий бар’єр і т. д. Після кожного етапу один із потоків підбиває його результати, викликаючи функцію done( ) для цього етапу.
Реалізація бар’єрів у POSIX
Ситуація із реалізацією бар’єрів схожа на ситуацію із блокуваннями читання-записування. Бар’єри можуть бути доволі просто реалізовані з використанням м’ютексів та умовних змінних (приклад такої реалізації наведено на сайті підтримки), але зараз POSIX і, приміром, NPTL пропонують їхню підтримку як базових примітивів, що відповідають нашому опису і можуть бути використані безпосередньо.
Для роботи із бар’єрами використовують тип pthread_barrier_t. ініціалізацію бар’єра здійснюють за допомогою функції pthread_barrier_init( ). За параметри вона приймає покажчик на структуру даних бар’єра і кількість потоків, яких можна очікувати на цьому бар’єрі:
Очікування на бар’єрі відбувається у функції pthread_barrier_wait( ):
Внаслідок переходу бар’єра одному потокові повертають спеціальне значення PTHREAO_BARRIER_SERIAL_THREAD, усім іншим - нуль.
Якщо бар’єр не потрібний або треба змінити кількість потоків, слід спочатку вилучити об’єкт із пам’яті та звільнити всі його ресурси. Для цього використовують функцію pthread_barrier_destroy( ):
Ми ще повернемося до бар’єрів під час розгляду підходу ведучого-веденого.
