Systemy operacyjne
Procesy i wątki
Dr inż. Lucjan Miękina
upel.agh.edu.pl/wimir/login/
Katedra Robotyki i Mechatroniki
January 21, 2013
1/18
Procesy i wątki
Wątki
Zwykły proces posiada pojedynczy wątek. Gdyby proces posiadał wiele wątków,
mógłby wykonywać wiele zadań równocześnie.
Wątek
Wątek jest podstawową jednostką wykorzystującą procesor; składa się z identyfikatora
wątku, licznika programu, zbioru rejestrów i stosu[1]. Wątek używa wspólnie z innymi
wątkami należącymi do tego samego procesu sekcji kodu, sekcji danych i innych
zasobów systemu operacyjnego, takich jak otwarte pliki i sygnały.
Rysunek poniżej pokazuje różnice między tradycyjnym procesem jednowątkowym a
procesem wielowątkowym.
2/18
Procesy i wątki
Wątki - przesłanki użycia
Podstawową przesłanką użycia wątków jest polepszenie wydajności programów.
Porównując koszt zarządzania procesami i wątkami, widać że obsługa wątków
wymaga znacznie mniej zasobów ze strony systemu operacyjnego.
Na przykład, porównując czasy wymagane do wykonania funkcji fork i
pthread_create widać że tworzenie wątku z użyciem pthread_create jest co
najmniej 10 razy szybsze.
Wszystkie wątki należące do procesu używają tej samej przestrzeni adresowej.
Komunikacja między wątkami jest bardziej wydajna i zwykle prostsza w realizacji
od komunikacji między procesami.
Aplikacje wielowątkowe pozwalają uzyskać zwiększoną wydajność i korzyści
użytkowe poprzez:
Nakładanie działania procesora i operacji we/wy: np., jeśli program ma
fragmenty w których wykonuje długotrwałą operację we/wy. Podczas gdy
jeden wątek oczekuje na zakończenie operacji we/wy, inne wątki mogą
wykonywać intensywne obliczeniowo fragmenty kodu.
szeregowanie oparte o priorytety lub realizowane w czasie rzeczywistym:
ważniejsze zadania mogą być zlecane przed zadaniami mniejszej wagi lub
mogą je przerywać.
Asynchroniczna obsługa zdarzeń: zadania obsługujące zdarzenia o nieznanej
częstości i długości mogą być realizowane z przeplotem. Np., przeglądarka
internetowa może zarówno ściągać dane związane z poprzednimi żądaniami
jak i przyjmować nowe żądania.
3/18
Procesy i wątki
Wątki - kiedy i jak ich używać ?
Programy które są kandydatami do zastosowania wątków:
zawierają zadania które mogą być wykonywane równolegle, lub dane które mogą
być używane równocześnie przez wiele procedur
blokują się w oczekiwaniu na wykonanie długich operacji we/wy
używają długich cykli procesora w pewnych miejscach, a w innych nie
muszą obsługiwać asynchroniczne zdarzenia
pewne operacje mają wyższe priorytety od innych
Wątki mogą też być użyte dla aplikacji sekwencyjnych, aby emulować wykonanie
równoległe. Doskonałym przykładem jest przeglądarka internetowa, zwykle działająca
na komputerze o jednym procesorze. Musi ona obsługiwać wiele zródeł danych
równolegle (nawet w obrębie jednej strony www, a tym bardziej przy pracy z wieloma
stronami).
Istnieją następujące główne modele programów wielowątkowych:
Manager/worker: wątek główny (manager) przydziela zadania pozostałym
wątkom - wykonawcom (worker). Zwykle wątek główny obsługuje cały strumień
wejściowy i dzieli go na części, realizowane przez wątki wykonawcze. Dwie
główne formy modelu manager/worker to: statyczna i dynamiczna pula
wykonawców (static worker pool) i (dynamic worker pool).
Potok: całe zadanie zostaje podzielone na ciąg bardziej elementarnych operacji,
każda z nich jest obsługiwana kolejno, ale równolegle przez osobny wątek.
Modelowi temu dobrze odpowiada linia montażu samochodów.
Peer: podobny do modelu manager/worker, z tym że główny wątek po
zainicjowaniu wątków wykonawczych również bierze udział realizacji części
4/18
zadania.
Procesy i wątki
Obsługa wątków w standardzie POSIX
Nazwa Pthreads oznacza standard POSIX (IEEE 1003.1c) definiujący API dla
tworzenia i synchronizacji wątków. Jest to specyfikacja, a nie implementacja działania
wątków. Implementacji dostarczają poszczególne systemy operacyjne, w tym Solaris,
Linux, Mac OS X, itd. Bezpłatne implementacje są również dostępne dla systemu
Microsoft Windows.
Procedury składające się na Pthreads można podzielić na następujące główne grupy:
1
Zarządzanie wątkami: działają bezpośrednio na wątkach - tworząc je,
odłączając, ustawiając i odczytując atrybuty wątków
2
Mutexy: procedury synchronizacji, działające w trybie wzajemnego wykluczania
(mutual exclusion). Funkcje te pozwalają tworzyć, usuwać, zamykać i otwierać
obiekty synchronizujące (mutexy). Funkcje te są uzupełnione o funkcje
zarządzające atrybutami mutexów.
3
Zmienne warunkowe (condition variables): zapewniają komunikację między
wątkami używającymi mutexu, w oparciu o warunki definiowane przez
programistę.
4
Synchronizacja: procedury zarządzania blokadami odczytu i zapisu wspólnych
danych.
Konwencja nazewnicza: wszystkie identyfikatory w bibliotece Pthreads rozpoczynają
się od pthread_.
5/18
Procesy i wątki
Wątki w systemie Linux
Linux pozwala tworzyć wątki za pomocą funkcji systemowej clone. Jednakże Linux nie
rozróżnia procesów i wątków. Zasadniczo, Linux stosuje termin zadanie (task) a nie
proces lub wątek. Kiedy wywołuje się funkcję clone, należy podać zbiór flag, które
określają jak wiele wspólnego mają mieć zadanie macierzyste i zadania potomne.
Najważniejsze z tych flag to:
flaga znaczenie
CLONE_FS wspólna informacja o systemie plików
CLONE_VM wspólna przestrzeń adresowa
CLONE_SIGHAND wspólna obsługa sygnałów
CLONE_FILES wspólna lista otwartych plików
Na przykład, jeśli funkcja clone otrzyma flagi CL0NE_FS, CLONE_VM,
CLONE_SIGHAND i CLONE_FILES, to w rezultacie utworzony zostanie wątek,
ponieważ zadanie macierzyste i potomne mają większość zasobów wspólnych.
Jeśli żadna z w.wym. flag nie zostanie użyta w wywołaniu clone, zadania nie będą
miały wspólnych zasobów i uzyskuje się rezultat jaki zapewnia funkcja fork.
Tak dokładna kontrola jest możliwa dzięki sposobowi reprezentowania zadania w
jądrze Linux-a. Struktura task_struct, zamiast danych dot. zadań, zawiera wskazniki
do innych struktur, w których przechowuje się te dane na przykład, listę otwartych
plików, bloki pamięci.
W trakcie wywołania funkcji clone tworzone jest nowe zadanie, reprezentowane przez
strukturę task_struct, w której pola wskaznikowe odpowiadające flagom ustawionym
w wywołaniu funkcji clone mają wartości identyczne jak w procesie macierzystym.
6/18
Procesy i wątki
Wątki w systemie Windows
System Windows pozwala tworzyć wątki za pomocą funkcji systemowej CreateThread.
1 #include
12 int main(int argc, char *argv[]) {
2 #include 13 DWORD ThreadId;
3 // data shared by the thread(s) 14 HANDLE ThreadHandle;
4 DWORD Sum; 15 int Param;
5 // the thread runs in this function 16 // perform some basic error checking
6 DWORD WINAPI Summation(LPVOID Param) { 17 if (argc < 2) {
7 DWORD Upper = *(DWORD*)Param; 18 fprintf(stderr,"USAGE: threads \n");
8 for (DWORD i=0; i<=Upper; i++) 19 return -1;
9 Sum += i ; 20 }
10 return 0; 21 Param = atoi(argv[1]) ;
11 } 22 if (Param < 0) {
23 fprintf(stderr,"An integer >= 0 required\n");
24 return -1;
25 }
26 // create the thread
27 ThreadHandle = CreateThread(
28 NULL, // default security attributes
29 0, // default stack size
30 Summation, // thread function
31 &Param, // parameter to thread function
32 0, // default creation flags
33 &ThreadId); // returns the thread identifier
34 if (ThreadHandle != NULL) {
35 // now wait for the thread to finish
36 WaitForSingleObject(ThreadHandle, INFINITE);
37 // close the thread handle
38 CloseHandle(ThreadHandle);
39 printf("sum = %d\n", Sum);
40 }
41 }
7/18
Procesy i wątki
Obsługa wątków w standardzie POSIX
W przykładzie pokazano sposób tworzenia 5 wątków za pomocą funkcji
pthread_create. Każdy wątek wyświetla komunikat "Hi, I m thread n!", a następnie
kończy się po wywołaniu funkcji pthread_exit.
lm@arch:~% gcc -pthread pthreads.c
1 #include
lm@arch:~% ./a.out
2 #include
MAIN: creating thread 1
3 #define NUM_THREADS 5
MAIN: creating thread 2
4
5 void *Hi(void *id) { MAIN: creating thread 3
6 printf("Hi, I m thread %ld!\n",
MAIN: creating thread 4
7 *((long*)id));
Hi, I m thread 2!
8 pthread_exit(NULL);
MAIN: creating thread 5
9 }
10
Hi, I m thread 4!
11 int main(int argc, char *argv[]) {
Hi, I m thread 3!
12 pthread_t threads[NUM_THREADS];
Hi, I m thread 1!
13 long tid[NUM_THREADS];
14 int rc, t; Hi, I m thread 5!
15 for(t=0; t16 tid[t]=t+1;
17 printf("MAIN: creating thread %ld\n", tid[t]);
18 rc = pthread_create(&threads[t], NULL, Hi,
19 (void*)&tid[t]);
20 if (rc) {
21 printf("ERROR in pthread_create(): %d\n", rc);
22 exit(-1);
23 }
24 }
25 // Last thing that main() should do
26 pthread_exit(NULL);
27 }
8/18
Procesy i wątki
Bezpieczeństwo wątków
Bezpieczeństwo wątków, oznacza zdolność aplikacji do równoczesnego wykonywania
wielu wątków bez powodowania "zaburzeń" wartości wspólnych danych lub hazardów.
Przypuśćmy na przykład, że aplikacja tworzy pewną liczbę wątków, które odwołują się
do tej samej procedury bibliotecznej:
Procedura odwołuje się do globalnej zmiennej w celu odczytu lub modyfikacji
wartości danej.
Jako że każdy wątek może wywołać tę procedurę, możliwe jest wystąpienie
równoczesnego dostępu do wspólnej zmiennej.
Jeśli procedura nie wykorzystuje żadnej metody synchronizacji aby przeciwdziałać
uszkodzeniu wspólnych danych, nie jest ona bezpieczna dla wątków (thread-safe).
Dlatego używając zewnętrznych bibliotek, należy zwracać uwagę na to, czy dostępne
tam procedury są zaprojektowane do zastosowania w aplikacjach wielowątkowych.
Rekomendacja: Gdy dokumentacja biblioteki tego nie precyzuje, należy zwykle przyjąć
że procedury nie są bezpieczne dla wątków.
9/18
Procesy i wątki
Przekazywanie argumentów do wątków
Poniższy przykład ilustruje sposób przekazywania wielu argumentów za pomocą
struktury. Każdy wątek otrzymuje unikalny egzemplarz tej struktury.
1 #include 18 int main() {
2 #include 19 person sk[] = { { "St.", "Grapelli", 70},
3 #include 20 {"J.-L.", "Ponty", 55},
4 21 { "M.", "Urbaniak", 60} };
5 typedef struct { 22 int cnt = sizeof(sk) / sizeof(person);
6 char* name; 23 pthread_t threads[cnt];
7 char* surname; 24 int rc, t;
8 int age; 25 for(t=0; t9 } person; 26 printf("MAIN: creating thread %d\n", t);
10 27 rc = pthread_create(&threads[t], NULL,
11 void *worker(void *data) { 28 worker, (void*)&sk[t]);
12 person* p = (person *) data; 29 if(rc) {
13 printf("\n- %s %s is %d", 30 printf("ERROR in pthread_create(): %d\n", rc);
14 p->name, p->surname, p->age); 31 exit(-1);
15 pthread_exit(NULL); 32 }
16 } 33 }
34 // Last thing that main() should do
35 pthread_exit(NULL);
36 }
lm@arch:~% gcc -pthread pthreads_args.c
lm@arch:~% ./a.out
- J.-L. Ponty is 55
- St. Grapelli is 70
- M. Urbaniak is 60
10/18
Procesy i wątki
Zmienne wykluczające - semafory (mutex)
Mutex jest skrótem terminu "mutual exclusion". Zmienne wykluczające
(semafory) są podstawowym sposobem synchronizowania wątków w celu
zabezpieczenia się przed równoczesnym dostępem z wielu wątków.
Semafor działa jak "zamek" strzegący dostępu do wspólnego zasobu.
Podstawowa zasada działania semaforów w bibliotece Pthreads polega na tym, że
tylko jeden wątek może zamknąć (lub zająć) semafor w danym okresie czasu.
Tak więc, nawet jeśli więcej wątków próbuje zająć semafor, tylko jeden wątek
może to zrobić. Trwa to do momentu gdy semafor zostanie zwolniony, więc inne
wątki muszą czekać na dostępność.
Semafory można stosować do uniknięcia hazardów (race condition). Przykład
hazardu w dostępie/aktualizacji salda rachunku bankowego:
Wątek 1 Wątek 2 Saldo
Odczyt salda: $1000 $1000
Odczyt salda: $1000 $1000
Depozyt: $100 $1000
Depozyt: $100 $1000
Aktualizacja salda: $1000+$100 $1100
Aktualizacja salda: $1000+$100 $1100
W tej sytuacji należałoby użyć semafora, aby zablokować dostęp do zmiennej
"Saldo" gdy jest ona przedmiotem transakcji.
11/18
Procesy i wątki
Semafory wykluczające
Poniższy przykład ilustruje zastosowanie semaforów wykluczających w programie
wielowątkowym, który wykonuje transakcje bankowe (depozyt środków).
1 #include 21 int main(int argc, char *argv[]) {
2 #include 22 pthread_t threads[NUM_THREADS];
3 #define NUM_THREADS 3 23 double deposits[]= {10., 20., 30.};
4 24 int rc, t;
5 double Balance=0.; 25 void *status;
6 pthread_mutex_t mutex; 26 for(t=0; t7 27 rc = pthread_create(&threads[t], NULL,
8 void *Deposit(void *par) { 28 Deposit, (void*)&deposits[t]);
9 pthread_mutex_lock (&mutex); 29 if (rc) {
10 printf("Deposit: "); 30 printf("pthread_create(): ERROR %d\n", rc);
11 double amount=*((double*)par); 31 exit(-1);
12 double old=Balance; 32 } }
13 printf("(%2.0lf+%2.0lf)=", 33 // Wait on the other threads
14 old, amount); 34 for(t=0; t15 Balance=old+amount; 35 pthread_join(threads[t], &status);
16 printf("%2.0lf\n", Balance); 36 // After joining, print out the result
17 pthread_mutex_unlock (&mutex); 37 printf("Balance=%2.0lf\n", Balance);
18 pthread_exit(NULL); 38 pthread_exit(NULL);
19 } 39 }
Semafor zostaje zamknięty w linii 9 Linie 9 i 16 są zakomentowane (synchronizacja
i zwolniony w linii 16: wyłączona):
Deposit: ( 0+20)=20 Deposit: ( 0+20)=20
Deposit: (20+10)=30 Deposit: Deposit: (20+30)=50
Deposit: (30+30)=60 (20+10)=30
Balance=60 Balance=30
12/18
Procesy i wątki
Lokalne dane wątków
Wątki danego procesu posiadają wspólne dane. Stanowi to podstawową korzyść tego
modelu. Jednakże w pewnych sytuacjach, wątki mogą wymagać własnych, prywatnych
danych. Nazywa się je danymi lokalnymi dla wątku (thread-specific data).
Na przykład, w systemie transakcyjnym, często powołuje się oddzielny wątek dla
każdej transakcji. Ponadto, każda transakcja posiada unikalny identyfikator. Aby
związać ten identyfikator z wątkiem, używa się danych lokalnych wątku .
1 #define _MULTI_THREADED 19 #define MAX_LEN 256
2 #include 20 typedef struct {
3 #include 21 int Len;
4 #include 22 char Array[MAX_LEN];
5 #include 23 } thread_data_t;
6 #include 24
7 25 #define NUMTHREADS 2
8 // Functions that use the thread specific data 26 pthread_key_t threadKey;
9 void print(); 27 pthread_mutex_t mutex;
10 void dataDestructor(void *data);
11
12 #define checkResults(string, val) { \
13 if (val) { \
14 printf("Failed with %d at %s", val, string); \
15 exit(1); \
16 } \
17 }
13/18
Procesy i wątki
Lokalne dane wątków
29 void *theThread(void *parm) {
30 int rc;
31 rc = pthread_setspecific(threadKey, (thread_data_t *)parm);
32 checkResults("pthread_setspecific()\n", rc);
33 pthread_mutex_lock(&mutex);
34 print();
35 pthread_mutex_unlock(&mutex);
36 return NULL;
37 }
38
39 void print() {
40 thread_data_t *pData = pthread_getspecific(threadKey);
41 printf("Thread %.8x: ", pthread_self());
42 int i;
43 for(i=0; i< pData->Len; i++)
44 printf("%c", pData->Array[i]);
45 printf("\n");
46 }
47
48 void dataDestructor(void *data) {
49 printf("Thread %.8x: Free data\n", pthread_self());
50 pthread_setspecific(threadKey, NULL);
51 free(data);
52 }
14/18
Procesy i wątki
Lokalne dane wątków
54 int main(int argc, char *argv[]) {
55 if(argc<3) {
56 fprintf(stderr, "USAGE: ./a.out ");
57 exit(-1);
58 }
59 pthread_t thread[NUMTHREADS];
60 int rc=0, i;
61 rc = pthread_key_create(&threadKey, dataDestructor);
62 checkResults("pthread_key_create()\n", rc);
63 for(i=0; i64 thread_data_t *pData = (thread_data_t *)malloc(sizeof(thread_data_t));
65 strcpy(pData->Array, argv[i+1]);
66 pData->Len = strlen(argv[i+1]);
67 rc = pthread_create(&thread[i], NULL, theThread, pData);
68 checkResults("pthread_create()\n", rc);
69 }
70 // Wait for the threads to complete, and release their resources
71 for(i=0; i 72 rc = pthread_join(thread[i], NULL);
73 checkResults("pthread_join()\n", rc);
74 }
75 pthread_key_delete(threadKey);
76 return 0;
77 }
15/18
Procesy i wątki
Zmienne warunkowe - (condition variables)
Zmienne warunkowe są następnym sposobem synchronizacji.
Podczas gdy semafory implementują synchronizację kontrolując dostęp do
danych, zmienne warunkowe pozwalają synchronizować dostęp w oparciu o
wartości danych.
Bez możliwości stwarzanych przez zmienne warunkowe, programista musiałby
ciągle odczytywać stan pewnych warunków (używając techniki zwanej polling,
zwykle w sekcji krytycznej), aby stwierdzić czy są one spełnione. Byłoby to
powodem znacznego obciążenia systemu. Zmienne warunkowe pozwalają uzyskać
analogiczny efekt bez stosowania polling-u.
Zmienne warunkowe zawsze stosuje się łącznie z semaforami.
16/18
Procesy i wątki
Zmienne warunkowe - typowy ciąg operacji
Wątek główny
Deklaruje oraz inicjalizuje globalne zmienne z wymaganą synchronizacją (takie jak "count")
Deklaruje oraz inicjalizuje obiekt zmiennej warunkowej
Deklaruje oraz inicjalizuje mutex związany z obiektem zmiennej warunkowej
Tworzy wątki A oraz B
Wątek A Wątek B
Wykonuje swoją pracę dopóki nie zajdzie Wykonuje swoją pracę
określony warunek (np. "count" ma określoną
wartość)
Blokuje mutex związany ze zmienną warunk- Blokuje mutex związany ze zmienną warunk-
ową oraz sprawdza wartość zmiennej globalnej ową
Wywołuje pthread_cond_wait aby zacząć Zmienia wartość zmiennej globalnej, na którą
oczekiwanie na sygnał od wątku B. Zwróć czeka wątek A
uwagę że wywołanie to w sposób au-
tomatyczny i atomowy odblokowuje wcześniej
zablokowany mutex związany ze zmienną
warunkową, przez co mutex ten może zostać
wykonywany przez wątek B
Gdy nadejdzie sygnał, wątek budzi się i au- Sprawdza wartość zmiennej globalnej, na
tomatycznie oraz atomowo blokuje mutex którą czeka wątek A. Jeśli warunek za-
kończenia czekania jest spełniony to wysyła
sygnał do wątku A
Odblokowuje mutex związany ze zmienną Odblokowuje mutex związany ze zmienną
warunkową warunkową
Kontynuuje działanie Kontynuuje działanie
Wątek główny
Aączy się z zakończonymi wątkami i kontynuuje działanie
17/18
Literatura
Blaise Barney. POSIX Threads Programming. 2 Ed. 7000 East Ave.,
Livermore, CA 94550-9234: Lawrence Livermore National Laboratory,
2012.
18/18
Wyszukiwarka
Podobne podstrony:
Wyklad PI 5
Wyklad PI 1 cz 2
Wyklad PI
Wyklad PI 9
Wyklad PI 3
Wyklad PI 2 cz 2
Wyklad PI 4
Wyklad PI 8
Wyklad PI 2 cz 1
Sieci komputerowe wyklady dr Furtak
Wykład 05 Opadanie i fluidyzacja
WYKŁAD 1 Wprowadzenie do biotechnologii farmaceutycznej
mo3 wykladyJJ
więcej podobnych podstron