- •9.3. Створення процесів
- •Базові поняття процесів і потоків
- •1.1. Процеси і потоки в сучасних ос
- •1.2. Моделі процесів і потоків
- •1.3. Складові елементи процесів і потоків
- •2. Багатопотоковість та її реалізація
- •2.1. Поняття паралелізму
- •2.2. Види паралелізму
- •2.3. Переваги і недоліки багато потоковості
- •2.4. Способи реалізації моделі потоків
- •3. Стани процесів і потоків
- •4. Опис процесів і потоків
- •4.1. Керуючі блоки процесів і потоків
- •4.2. Образи процесу і потоку
- •5. Перемикання контексту й обробка переривань
- •5.1. Організація перемикання контексту
- •5.2. Обробка переривань
- •6. Створення і завершення процесів і потоків
- •6.1. Створення процесів
- •6.2. Ієрархія процесів
- •6.3. Керування адресним простором під час створення процесів
- •6.4. Особливості завершення процесів
- •6.5. Синхронне й асинхронне виконання процесів
- •6.6. Створення і завершення потоків
- •7. Керування процесами в unix і Linux
- •7.1. Образ процесу
- •7.2. Ідентифікаційна інформація та атрибути безпеки процесу
- •7.3. Керуючий блок процесу
- •7.5. Завершення процесу
- •7.7. Запуск програми
- •7.8. Очікування завершення процесу
- •7.9. Сигнали
- •8. Керування потоками в Linux
- •8.1. Базова підтримка багато потоковості
- •8.2. Особливості нової реалізації багато потоковості в ядрі Linux
- •8.3. Потоки ядра Linux
- •8.4. Програмний інтерфейс керування потоками posix
- •9. Керування процесами у Windows хр
- •9.1. Складові елементи процесу
- •9.2. Структури даних процесу
- •9.3. Створення процесів
- •9.4. Завершення процесів
- •9.5. Процеси і ресурси. Таблиця об’єктів процесу
- •9.6. Програмний інтерфейс керування процесами Win32 арі
- •10. Керування потоками у Windows хр
- •10.1. Складові елементи потоку
- •10.2. Структури даних потоку
- •10.3. Створення потоків
- •10.4. Особливості програмного інтерфейсу потоків
- •Висновки
- •Контрольні запитання та завдання
8.2. Особливості нової реалізації багато потоковості в ядрі Linux
Нова реалізація багатопотоковості у ядрі системи має такі особливості.
♦ Підвищилася продуктивність операції створення і завершення потоків. Знято обмеження на загальну кількість потоків у системі. Система залишається стабільною за умов одночасного створення і завершення сотень тисяч потоків (за наявності достатнього обсягу оперативної пам’яті).
♦ Усі потоки процесу тепер повертають один і той самий ідентифікатор (pid), крім того, зв’язок «предок-нащадок» між ними не підтримується (у створеного потоку зберігається той самий предок, що й у потоку-творця). Як процес у системі реєструють тільки початковий потік застосування.
♦ Реалізацію виклику clone( ) розширено таким чином, щоб зробити непотрібним потік-менеджер.
Сьогодні нові засоби інтегровані у тестову версію ядра. Доступна також онов¬ена реалізація бібліотеки потоків, яка дістала назву NPTL (Native POSIX Threads Library).
Бібліотека підтримки потоків NPTL
Бібліотека NPTL призначена для того, щоб, спираючись на нові можливості ядра, забезпечити повну й коректну реалізацію стандарту потоків POSIX для використання у прикладних програмах.
Основною особливістю бібліотеки NPTL є те, що в ній збереглася підтримка схеми багатопотоковості 1:1, внаслідок чого досягається простота та надійність реалізації. При цьому продуктивність і масштабованість забезпечені оновленою підтримкою багатопотоковості у ядрі. Програмний інтерфейс бібліотеки не змінився порівняно з LinuxThreads.
8.3. Потоки ядра Linux
Крім процесів і потоків користувача, Linux також підтримує спеціальний вид планованих об’єктів, які мають уже знайому назву — потоки ядра Такі потоки пла-нують як звичайні процеси і потоки, кожен з яких має свій ідентифікатор (pid). Відмінності потоків ядра від процесів і потоків користувача полягають у тому, що:
♦ функції потоку для них визначають у коді ядра;
♦ вони виконуються тільки в режимі ядра;
♦ для них недоступні ділянки пам’яті, виділені в режимі користувача.
Прикладом потоку ядра Linux є kswapd. Цей потік використовують під час керування пам’яттю, його роботу буде розглянуто пізнійше.
8.4. Програмний інтерфейс керування потоками posix
Керування потоками є частиною стандарту POSIX з 1996 року, відповідний програмний інтерфейс дістав назву потоки POSIX (POSIX threads). Цей стандарт реалізовано у більшості сучасних UNIX-сумісних систем, у Linux його підтримка була частково реалізована в бібліотеці LinuxThreads і значно повніше — у бібліотеці NPTL.
Зупинимося на базових засобах керування потоками POSIX, відклавши розгляд засобів синхронізації на подальші лекції.
Усі типи даних і функції, визначені стандартом, мають префікс pthread_, для їхнього використання необхідно підключати заголовний файл pthread.h.
Створення потоків POSIX
Щоб додати новий потік у поточний процес, у POSIX використовують функцію pthread_create( ) із таким синтаксисом:
Розглянемо параметри цієї функції:
♦ th — покажчик на заздалегідь визначену структуру типу pthread t, яка далі буде передана в інші функції роботи з потоками; далі називатимемо її дескриптором потоку (thread handle);
attr — покажчик на структуру з атрибутами потоку (для використання атрибутів за замовчуванням потрібно передавати нульовий покажчик, деякі атрибути розглянемо пізніше);
♦ thread_fun - покажчик на функцію потоку, що має описуватися як
♦ arg — дані, що передаються у функцію потоку (туди вони потраплять як параметр value).
Ось приклад створення потоку POSIX:
Для потоків, створених у такий спосіб, розмір стека задають за замовчуванням. Крім того, після завершення їх обов’язково треба приєднати.
Новий потік починає виконуватися паралельно із потоком, що його створив. Наприклад, якщо всередині функції main( ) був створений потік, то виконання продовжать два потоки: початковий програми, шо виконує код main ( ), і новий.
Завершення потоків POSIX
Для завершення потоку достатньо дійти до кінця його функції потоку thread_fun( ) або викликати з неї pthread_exit( ):
Параметр retval задає код повернення. Наведемо приклад завершення потоку:
Для коду початкового потоку програми є дві різні ситуації:
♦ виконання оператора return або виконання всіх операторів до кінця
main( ) завершує весь процес, при цьому всі потоки теж завершують своє виконання;
♦ виконання функції pthread_exit( ) усередині функції main( ) завершує тільки початковий потік, ця дія не впливає на інші потоки у програмі - вони продовжуватимуть виконання, поки самі не завершаться.
Виклик функції pthread_exit() використовують тільки для приєднуваних потоків, оскільки він не звільняє ресурси потоку — за це відповідає той, хто приєднає потік. Насправді, якщо такий потік не буде приєднаний негайно після завершення, його інформація залишиться в системі й може бути зчитана пізніше.
Приєднання потоків POSIX
Для очікування завершення виконання потоків використовують функцію pthread _join( ). Вона має такий синтаксис:
Ця функція блокує потік, де вона була викликана, доти, поки потік, заданий дескриптором th, не завершить своє виконання. Потік, на який очікують, має існувати в тому самому процесі й не бути від’єднаним. Якщо status не є нульовим покажчиком, після виклику він вказуватиме на дані, повернені потоком (аргумент функції pthread_exit( )). Якщо потік уже завершився до моменту виклику функції pthread_join( ), його інформація має бути зчитана негайно.
Після приєднання потоку інформацію про нього повністю вилучають із системи, тому коли кілька потоків очікували на той самий потік, його приєднає тільки один із них, для решти буде повернено помилку (до того моменту потрібного потоку в системі вже не існуватиме). Таких ситуацій краще уникати.
Розглянемо приклад приєднання потоку. Припустимо, що треба асинхронно запустити доволі тривалий запит до бази даних, виконати паралельно з ним деяку роботу (наприклад, відобразити індикатор виконання), після чого дочекатися результатів запиту. Пропоноване рішення зводиться до створення окремого потоку для виконання запиту (потоку-помічника, helper thread) і до очікування його завершення у базовому потоці. Ось приблизний програмний код:
Часто програмісти забувають виконувати операцію приєднання під час завершення приєднуваних потоків (появі таких помилок сприяє й те, що за замовчуванням усі потоки є приєднуваними). У підсумку ресурси таких потоків не звільняються системою, що призводить до витікання пам’яті.
Від’єднані потоки. Задання атрибутів потоків POSIX
Для того, щоб отримати від'єднаний потік, треба задати відповідні атрибути потоку, виконавши такі дії.
1.Описати змінну для їхнього зберігання (атрибути потоку відображає структура даних pthread_attr_t):
2.Ініціалізувати цю змінну так:
3. Задати атрибут за допомогою виклику спеціальної функції (різним атрибутам відповідають різні функції).
Для задання атрибута, який визначає, чи буде цей потік від’єднаним чи ні, використовують таку функцію:
Другим параметром може бути одне зі значень: РТНRЕAD_ СREАТЕ_J 0ІNABLЕ (приєднуваний потік); PTHREAD_CREATE_DETACHED (від’єднаний потік).
Ось приклад задання атрибута і його використання під час створення від’єднаного потоку:
Завершуються такі потоки виходом із функції потоку.
