Нитки що таке нитка

У попередньому підрозділі ми торкнулися питання ниток, розглянемо його детальніше.

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

Створення нитки і ідеологія posix api

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

int pthread_create (pthread_t * thread, const pthread_attr_t * attr, void * (* start) (void *), void * arg)

Спрощено виклик pthread_create [thr, NULL, start, NULL] створить нитка яка почне виконувати функцію start і запише в змінну thr ідентифікатор створеної нитки. На прикладі цього виклику ми докладно розглянемо кілька допоміжних концепцій POSIX API з тим, щоб не зупинятися на них далі.

Перший аргумент цієї функції thread - це покажчик на змінну типу pthread_t, в яку буде записаний ідентифікатор створеної нитки, який надалі можна буде передавати іншим викликам, коли ми захочемо зробити що-небудь з цією ниткою. Тут ми стикаємося з першою особливістю POSIX API, а саме з непрозорістю базових типів. Справа в тому, що ми практично нічого не можемо сказати про тип pthread_t. Ми не знаємо ціле чи це або покажчик? Ми не можемо сказати чи існує впорядкованість між значеннями цього типу, тобто чи можна вибудувати з них неубутних ланцюжок. Єдине що сказано в стандарті, це що ці значення можна копіювати, і що використовуючи виклик int pthread_equal [pthread_t thr1, pthread_t thr2] ми можемо встановити що обидва ідентифікатора thr1 і thr2 ідентифікують одну і ту ж нитку [при цьому вони цілком можуть бути нерівні в сенсі оператора рівності]. Подібними властивостями володіє більшість типів використовуваних в даному стандарті, більш того, як правило, значення цих типів навіть не можна копіювати!

Другий аргумент цієї функції attr - це покажчик на змінну типу pthread_attr_t, яка задає набір деяких властивостей створюваної нитки. Тут ми стикаємося з другою особливістю POSIX API, а саме з концепцією атрибутів. Справа в тому, що в цьому API у всіх випадках, коли при створенні або ініціалізації деякого об'єкту необхідно задати набір якихось додаткових його властивостей, замість вказівки цього набору за допомогою набору параметрів виклику використовується передача попередньо сконструйованого об'єкта представляє цей набір атрибутів. Таке рішення має, принаймні, дві переваги. По-перше, ми можемо зафіксувати набір параметрів функції без загрози його зміни в подальшому, коли у цього об'єкта з'являться нові властивості. По-друге, ми можемо багато разів використовувати один і той же набір атрибутів для створення безлічі об'єктів.

Третій аргумент виклику pthread_create це покажчик на функцію типу void * () [void *]. Саме цю функцію і починає виконувати новостворена нитка, при цьому в якості параметра цієї функції передається четвертий аргумент виклику pthread_create. Таким чином можна з одного боку параметризрвані створювану нитка кодом який вона буде виконувати, з іншого боку параметризрвані її різними даними переданими коду.

Функція pthread_create повертає нульове значення в разі успіху і ненульовий код помилки в разі невдачі. Це також одна з особливостей POSIX API, замість стандартного для Unix підходу коли функція повертає лише деякий індикатор помилки а код помилки встановлює в змінної errno, функції Pthreads API повертають код помилки в результаті свого аргументу. Очевидно, це пов'язано з тим що з появою в програмі декількох ниток викликають різні функції повертають код помилки в одну і ту ж глобальну змінну errno, настає повна плутанина, а саме немає ніякої гарантії що код помилки який зараз перебуває в цієї змінної є результатом виклику події в цій а не інший нитки. І хоча через величезну числа функцій вже використовують errno бібліотека ниток і забезпечує по екземпляру errno для кожної нитки, що в принципі можна було б використовувати і в самій бібліотеці ниток, проте творці стандарту вибрали більш правильний а головне більш швидкий підхід при якому функції API просто повертають коди помилки.