Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
LAB_3.doc
Скачиваний:
1
Добавлен:
28.08.2019
Размер:
77.31 Кб
Скачать

Лабораторна робота №3 Тема: «Процеси і потоки»

Процеси і потоки

На найвищому рівні абстракції система складається з безлічі процесів. Кожен процес відповідальний за забезпечення службових функцій певного характеру|вдачі|, незалежно від того, чи є|з'являється| він елементом файлової системи, драйвером дисплея, модулем збору|збирання| даних, модулем управління або чим-небудь ще.

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

Чому процеси?

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

  • можливість|спроможність| декомпозиції завдання|задачі| і модульної організації рішення|розв'язання|;

  • зручність супроводу;

  • надійність.

Концепція розділення|поділу| завдання|задачі| на частини|на шматки|, тобто, декілька незалежних завдань|задачі|, є|з'являється| дуже могутньою. І саме така концепція лежить в основі QNX|. Операційна система QNX| складається з безлічі незалежних модулів, кожен з яких наділений деякою зоною відповідальності. Ці модулі незалежні і реалізовані в окремих процесах. Єдина можлива встановити залежність цих модулів один від одного — налагодити між ними інформаційний зв'язок за допомогою невеликої кількості строго|суворий| певних інтерфейсів.

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

Запуск процесу

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

Запуск процесу з|із| командного рядка

Наприклад, при запуску процесу з|із| командного інтерпретатора ви можете ввести|запроваджувати| командний рядок:

$ program1|

Ця вказівка наказує|пропонує| командному інтерпретатору запустити програму program1| і чекати завершення її роботи. Або, ви могли набрати:

$ program2| &

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

Якщо ви побажаєте|забажаєте| скоректувати пріоритет програми до її запуску, ви можете застосувати команду nice| — точно так, як і в Unix|:

$ nice| program3|

Запуск процесу з|із| програми

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

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

Розглянемо|розглядуватимемо| деякі з функцій, які забезпечує для запуску інших процесів (або підміни одного процесу іншим):

  • system|();

  • сімейство функцій ехес|();

  • сімейство функцій spawn|();

  • fork|();

  • vfork|().

Яку з|із| цих функцій застосовувати, залежить від двох вимог: переносимості|переносимий| і функціональності. Як завжди, між цими двома вимогами можливий компроміс.

Зазвичай|звично| при всіх запитах на створення|створіння| нового процесу відбувається|походить| наступне|слідуюче|. Потік в первинному|початковому| процесі викликає|спричиняє| одну з наведених вище функцій. Зрештою|врешті решт| функція змусить|примусить| адміністратор процесів створити адресний простір|простір-час| для нового процесу. Потім ядро виконає запуск потоку в новому процесі. Цей потік виконає декілька інструкцій і викличе|спричинятиме| функцію main|().

У розглянутому прикладі після породження процесу – нащадка, батьківський процес видає виводить на термінал ідентифікатор породженого процесу, затримується на 5 секунд і викликає функцію для опиту стану процесу – нащадка. Породжений процес виводить повідомлення, що містить значення змінної x. Слід звернути увагу на те, що значення цієї змінною збігаються і у батька, і у нащадка.

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>

int main()

{

int x, pid;

x=2;

printf("Single process, x=%d\n",x);

pid=fork();

if(pid == 0)

printf("New, x=%d\n",x); // Нащадок

else if(pid > 0){ // Батько

printf("Old, pid=%d, x=%d\n",pid,x);

sleep(5);

wait(pid);

}

else {

perror("Fork error ");

return -1;

}

return 0;

}

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

execl(name,arg0,arg1...,argn,0)

char *name, *arg0, *arg1,...,*argn;

execv(name,argv)

char *name, *argv[];

execle(name,arg0,arg1...,argn,0,envp)

char *name, *arg0, *arg1,...,*argn,*envp[];

execve(name,argv,envp)

char *name, *arg[],*envp[];

Запуск потоку

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

Будь-який потік може створити інший потік в тому ж самому процесі; на це не накладаються|накладають| ніяких|жодних| обмежень (за винятком об'єму|обсягу| пам'яті, звичайно!) Найбільш загальний|спільний| шлях|колія| реалізації цього — використання виклику функцій pthread_create|():

#include| <pthread|.h>

int| int|

pthread_create| (pthread_t| *thread, const| pthread_attr_t| *attr, void| *(*start_routine|) (void| *), void| *arg);

Функція pthread_create|() має чотири аргументи :

  • thread| - покажчик на pthread_t|, де зберігається ідентифікатор потоку;

  • attr| - атрибутний запис;

  • start_routine| - підпрограма, з|із| якою починається|розпочинає| потік;

  • arg| - параметр, який передається підпрограмі start_routine|.

Відзначимо, що покажчик thread| і атрибутний запис (attr|) — необов'язкові елементи, ви можете передавати замість них NULL|.

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

Якби|аби| нам була до цього справа|річ|, ми б зробили так:

pthread_t| tid|;

pthread_create| (&tid, ...

printf| («Новий потік має ідентифікатор %d\n», tid|);

Таке застосування|вживання| досконале|довершене| типово, тому що|бо| вам часто може потрібно знати, який потік виконує яка ділянка коди.

Невеликий тонкий момент. Новий потік може почати|розпочинати| працювати ще до привласнення|присвоєння| значення параметру tid|. Це означає, що ви повинні уважно відноситися до використання tid| як глобальна змінна. У прикладі|зразку|, приведеному вище, все буде коректно, тому що|бо| виклик pthread_create|() відпрацював|відробляв| до використання tid|, що означає, що на момент використання tid| мав коректне значення.

Новий потік починає|розпочинає| виконання з функції start_routine| (), з|із| параметром arg|.

Атрибутний запис потоку

Коли ви здійснюєте запуск нового потоку, він може слідувати|прямувати| ряду|лаві| чітко певних установок за умовчанням, або ж ви можете явно задати його характеристики.

Перш, ніж ми перейдемо до обговорення завдання|задавання| атрибутів потоку, розглянемо|розглядуватимемо| тип|типа| даних

Синхронізація

Найпростіший метод синхронізації — це «приєднання» (joining|) потоків. Реальна ця дія означає очікування|чекання| завершення.

Приєднання виконується одним потоком, що чекає завершення іншого потоку. Потік, що чекає, викликає|спричиняє| pthreadjoin|():

#include| <pthread|.h>

int|

pthread_join| (pthread_t| thread|, void| **value_ptr);

Функції pthreadjoin|() передається ідентифікатор потоку, до якого ви бажаєте приєднатися, а також необов'язковий аргумент value_ptr|, який може бути використаний для збереження|зберігання| повертаного приєднуваним потоком значення. (Ви можете передати|передавати| замість цього параметра NULL|).

Де нам брати ідентифікатор потоку?

У функції pthread_create|() як перший аргумент покажчик на pthread_t|. Там і буде збережений ідентифікатор знов|знову| створеного потоку.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]