Lf281, SoftwareDevelopment Паралельне програмування - взаємодія між процесами

Студент Політехнічного Університету Мілана, навчається на факультеті телекомунікаційних технологій, працює мережевим адміністратором та цікавиться програмуванням (в основному на Ассемблері та C/C++). З 1999 майже завжди працює в Linux/Unix.

Переклад українською:Dmitry Martsynkevitch

Паралельне програмування – взаємодія між процесами.

softwaredevelopment

Метою цієї серії статей є ознайомлення читача з концепцією багатозадачності та її реалізацією в операційній системі Linux. Почавши з теоретичних основ, ми закінчимо написанням повноцінної програми, яка демонструє взаємодію між процесами, з простим, але ефективним протоколом комунікації.

Що необхідно знати для розуміння цієї статті:

  • Мінімальні знання shell
  • Основи мови C (синтаксис, цикли, бібліотеки)
Також вам слід прочитати першу статтю цього циклу, оскільки вона є основою для даної: November 2002, article 272._________________ _________________ _________________

Насправді проблема дещо складніша, ніж може здатися: це питання одночасної роботи програм, а й питання одночасного використання одних даних, як читання, так записи.

Поговоримо про деякі класичні проблеми одночасного використання даних; якщо два процеси одночасно читають один набір даних, це, очевидно, не створює проблем, і виконання процесів — послідовне. Нехай тепер один процес змінює набір даних: результат роботи другого процесу залежатиме від того, прочитав дані до або після їх зміни. Наприклад: у нас є два процеси "А" та "В" і ціле число "d". Процес А збільшує d на одиницю, Вдрукує значення d. Це можна записати умовною мовою так:

A < d-d+1 > & B < d->output >

тут "&" означає одночасне виконання процесів. Спочатку може бути виконаний процес А,

(-) d = 5 (A) d = 6 (B) output = 6

а може і процес В:

(-) d = 5 (B) output = 5 (A) d = 6

Відразу зрозуміло, наскільки важливо вміти правильно поводитися з такими ситуаціями: ризик суперечливості даних високий і неприйнятний. Якщо ви ще недооцінюєте цю проблему, уявіть, що набір даних — це ваш банківський рахунок.

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

Очевидно, це не найкращий спосіб: процес змушений простоювати в очікуванні завершення роботи другим процесом. Неприємність полягає в тому, що другий процес може працювати досить довго, а загальними даними користуватися короткий проміжок часу. Отже, необхідно збільшити " гранульованість " нашого управління, тобто. керувати окремими наборами даних. Вирішення цієї проблеми - примітиви зі стандартної бібліотеки, відомої як SysV IPC (Взаємодія процесів у System V).

Ключі SysV

key_t ftok(const char * pathname, int proj_id);

Для генерування ключа ftok бере ім'я наявного файлу (pathname) та ідентифікатор процесу (proj_id). Алгоритм побудови ключа не виключає можливості появи дублікатів, тому слід мати маленьку бібліотеку, яка переглядає вже створені ключі та не допускає повторень.

Семафори можуть використовуватися для контролю доступу до ресурсів: число вСемафор являє собою кількість процесів, які можуть отримати доступ до даних. Щоразу, коли процес звертається до даних, значення семафорі, має бути зменшено на одиницю, і збільшено, коли роботу з даними буде припинено. Якщо ресурс ексклюзивний, тобто до даних повинен мати доступ лише один процес, початкове значення в семафорі слід встановити одиницею.

Семафори можна використовувати і для інших цілей, наприклад, для лічильника ресурсів. У цьому випадку число в семафорі - кількість вільних ресурсів (наприклад, кількість вільних осередків пам'яті).

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

Розглянемо, як реалізовані семафори на C, SysV. Створює семафор функція semget(2)

int semget(key_t key, int nsems, int semflg);

тут key - IPC ключ, nsems - число семафорів, яке ми хочемо створити, і semflg - права доступу, закодовані в 12 біт: перші три біти відповідають за режим створення, решта дев'яти - права на запис і читання для користувача, групи та інших ( помітте подібність із файловою системою в Unix). За більш повною інформацією завітайте до man сторінки ipc(5). Як бачите SysV створює відразу кілька семафорів, що зменшує код.

Давайте створимо наш перший семафор Далі нам треба з'ясувати, як керувати семафорами, і як видаляти їх. Управління відбувається за допомогою функції semctl(2),

int semctl(int semid, int semnum, int cmd, . )

яка виконує дію cmd на наборі семафорів semid або (якщо потрібна команда) на одному семафорі з номером semnum. Ми розповімо про властивості цієї команди, коли станенеобхідно, повний список властивостей доступний на man сторінках. Залежно від команди, може знадобиться вказати ще один аргумент наступного типу: Щоб змінити значення семафору, використовують директиву SETVAL, нове значення має бути вказано у semun; давайте модифікуємо наведену вище програму, встановлюючи в семафорі значення 1. Тепер необхідно видалити семафор, звільняючи структури, що використовуються для керування ним; це виконує директива IPC_RMID. Вона видаляє семафор і надсилає повідомлення про це всім процесам, які очікують доступу до ресурсу. Останній раз змінимо програму: Як ви вже зрозуміли, створення та управління структурами контролю за паралельним виконанням програм досить просто, коли ми додамо обробку помилок, все стане дещо складніше, але лише в сенсі складності коду.

Використовувати семафор можна за допомогою процедури semop(2),

int semop(int semid, struct sembuf * sops, unsigned nsops);

тут semid - ідентифікатор набору семафорів, sops - масив, що містить операції, які необхідно зробити, nsops - кількість цих операцій. Кожна операція є структурою sembuf.

unsigned short sem_num; short sem_op; short sem_flg;

тобто номером семафора в множині (sem_num), операцією (sem_op) та прапором, що встановлює режим очікування; нехай поки він буде нулем. Операції, які ми можемо вказати, є цілими числами та підпорядковуються таким правилам:

  1. sem_op 0 Значення sem_op додається до значення у семафорі, використовуваний ресурс звільняється.
Наступна програма представляє приклад використання семафорів, реалізуючи попередній приклад із буфером: ми створимо п'ять процесів W та один процес R. Процеси W намагатимуться отримати доступ до ресурсу (буферу), закриваючи його черезсемафор, і, якщо буфер не сповнений, будуть класти в нього елемент та звільняти ресурс. Процес R закриватиме ресурс, братиме з нього елемент, якщо буфер не порожній, і розблокуватиме ресурс.

Читання та запис у буфер насправді несправжні: так відбувається тому, що, як обговорювалося в попередній статті, кожен процес виконується у своїй власній області пам'яті і не може звертатися до пам'яті іншого процесу. Це робить справжнє управління буфером шістьма процесами неможливим, оскільки кожен процес бачитиме свою копію буфера. все встане на свої місця, коли ми говоритимемо про пам'ять, що розділяється, але давайте бути послідовними.

Чому нам потрібні три семафори? Перший (з номером 0) діє як замок до буфера, і його максимальне значення дорівнює одиниці, інші два відповідають за переповнення та наявність елементів у буфері. Одним семафором цього не досягти.

Дії push та pop дещо відрізняються від перших: це масиви з двох дій. Перше дію над семафором номер 1, друге - над семафором номер 2; одне збільшує значення в семафорі, інше зменшує, але тепер процес не буде чекати на звільнення ресурсу: IPC_NOWAIT змушує його продовжити роботу, якщо ресурс заблокований. Тут ми ініціалізуємо значення семафорах: у першому — одиницею, оскільки він контролює доступом до ресурсу, у другому — довжиною буфера (заданої в командному рядку), у третьому — нулем (тобто. числом елементів у буфері). Процес W намагається заблокувати ресурс у вигляді дії lock_res; як тільки це йому вдається, він додає елемент буфер за допомогою дії push і виводить повідомлення про це на стандартний висновок. Якщо операцію не можна зробити, процес виводить повідомлення про заповнення буфера. Насамкінець процес звільняє ресурс. Процес R веде себепрактично так само як і W процес: блокує ресурс, здійснює дію pop, звільняє ресурс.