Pobierz prezentację
Pobieranie prezentacji. Proszę czekać
OpublikowałTymoteusz Matoga Został zmieniony 10 lat temu
1
Automatyka i Robotyka Systemy czasu rzeczywistego Wykład 4
2
Wątki Mechanizm wątków to sposób realizacji obliczeń współbieżnych Wątek - wykonywanie pewnej sekwencji instrukcji Dla systemu wątek jest obiektem - (osobną strukturą danych) Każdy wątek jest "zarejestrowany" w systemie Dla programisty wątek to procedura w programie wykonywana współbieżnie z inną.
3
.. a zatem Program współbieżny składa się z dwóch lub więcej podzadań (procesów, wątków) – wykonywanych sekwencyjnie, lecz ich instrukcje wykonawcze wzajemnie się przeplatają w czasie (mogą być też wykonywane w pełni równolegle np.. na maszynach wieloprocesorowych) Nie czynimy żadnych założeń o kolejności przeplotów – poza strzeżonymi fragmentami – tzw. sekcjami krytycznymi
4
Wykorzystanie praktyczne - dowolne problemy dla maszyn wieloprocesorowych – celem jest skrócenie czasu obliczeń -specjalne algorytmy – np. sortowanie przez podział zbiorów sortowanych -konieczność działań równoległych – np. programy wymagające równoczesnej kontroli wielu napływających sygnałów z otoczenia i ich przetwarzania
5
Sortowanie Dla zbioru N-licznego potrzeba dla posortowania N(N-1)/2 porównań NN(N-1)/2 1000 ~ 500 000 Jeśli podzielimy na 2 podzbiory N/2N/2(N/2-1)/2 500~62 500 500~62 500 + scalenie dwóch zbiorów 1 000 RAZEM 126 000.. a więc znaczny zysk – nawet gdybyśmy wykonywali w jednym procesie sekwencyjnie
6
W wielowątkowych programach współbieżnych łatwo o błędy Trzeba wyraźnie określić, które czynności mają być wykonywane równolegle Nie wolno np. rozpocząć scalania, dopóki nie zakończą pracę wątki składowe sortujące
7
Po uruchomieniu programu powstaje: proces - rezerwacja zasobów jeden wątek – rozpoczęcie wykonywania procedury głównej. W ramach jednego procesu może działać jeden lub więcej wątków. Kolejne wątki mogą być utworzone w trakcie pracy programu. Wątki utworzone przez proces stają się automatycznie jego "własnością", nie "żyją" dłużej niż ich właściciel Realizacja programowa
8
Elementy składowe zadania wykonywane współbieżnie z innymi A B C A B C współbieżnie sekwencyjnie przeplot instrukcji w czasie
9
Serwer cały czas nasłuchuje przychodzących połączeń (wątek główny). W chwili pojawienia się żądania połączenia przez klienta, tworzony jest nowy wątek, który zajmuje się jego obsługą. Wątek główny nadal nasłuchuje… Gdy wielu klientów – wiele wątków ich obsługi Inny przykład, o którym mówiliśmy wcześniej: Serwer HTTP
10
Nasłuchuj Utwórz HTML Wyślij HTML Utwórz HTML Wyślij HTML Wątek głównyWątek 2Wątek 3 Uruchamianie wątków kopii – wykonujących tę samą funkcję – z innymi argumentami
11
Każdy wątek w systemie posiada swój unikalny identyfikator. Standard POSIX definiuje specjalny typ danych pthread_t do reprezentacji identyfikatora wątku. Przy pomocy identyfikatora można wskazać na dowolny wątek w systemie. Do pobrania identyfikatora wątku można użyć procedury pthread_self() Możliwe jest również porównywanie dwu identyfikatorów funkcją pthread_equal(). Identyfikator wątku Korzystanie z mechanizmu obsługi wątków: plik nagłówkowy pthread.h
12
#include int main(int argc, char * argv[]) { pthread_t id; id = pthread_self(); printf("%u\n",id); return 0; } wypisanie identyfikatora wątku głównego
13
pthread_create(&s_id, &attr, funkcja, void* arg) gdzie: s_id - NULL, lub wskaźnik do obiektu typu pthread_t, attr – wskaźnik do struktury opisującej atrybuty tworzonego wątku, funkcja – nazwa procedury dla realizacji wątku, arg - argument przekazany do funkcji. Tworzenie wątku Funkcja pthread_create() o składni:
14
#include void* drugi(void* arg) { /* Procedura wykonywana przez watek */ pthread_t id; id = pthread_self(); printf("Drugi: %u\n",id); return NULL; } int main(int argc, char* argv[]) { pthread_t id; /* Identyfikator tego watku*/ pthread_t drugi_id; /* identyfikator tworzonego watku */ pthread_attr_t attr; /* Atrybuty watku */ /* Inicjalizuje strukture z atrybutami*/ pthread_attr_init( &attr ); /* Utworzenie watku - "drugi" to nazwa funkcji, ktora bedzie watkiem - funkcja drugi()*/ pthread_create(&drugi_id, &attr, drugi, NULL); /* Wyswietl identyfikator */ id = pthread_self(); printf("Glówny: %u\n", id); /* Czekaj na zakonczenie watku*/ pthread_join(drugi_id, NULL); return 0; }
15
#include void* drugi(void* arg) { pthread_t id; pthread_t main_id; id = pthread_self(); main_id = (int) arg; printf("Drugi: %u\n",id); printf("A to id ojca: %u\n", main_id); return NULL; } int main(){ pthread_t id; /* Identyfikator tego watku*/ pthread_t drugi_id; /* identyfikator tworzonego watku */ pthread_attr_t attr; /* Atrybuty watku */ pthread_attr_init( &attr ); /* Utworz nowy watek i przekaz 'id' jako argument */ pthread_create(&drugi_id, &attr, drugi, (void*) id); /* Czekaj na zakonczenie watku "drugi" */ printf("id drugiego: %u\n",drugi_id); return 0; } Przekazywanie argumentu liczbowego do wątku poprzez argument arg Pierwszy: 1 Drugi: 2 A to id ojca: 1 id drugiego: 2 tu: przekazanie id wątku głównego
16
#include void* drugi(void* arg) { pthread_t id; printf("Argument: %s\n",arg); id = pthread_self(); printf("Drugi: %u\n",id); return NULL; } int main(){ pthread_t id; /* Identyfikator tego watku*/ pthread_t drugi_id; /* identyfikator tworzonego watku */ pthread_attr_t attr; /* Atrybuty watku */ pthread_attr_init( &attr ); /* Utworz nowy watek i przekaz 'tekst' jako argument */ char *tekst = "Jakis tam tekst"; printf("%c\n", tekst[0]);//wypisze J pthread_create(&drugi_id, &attr, drugi, (void*) tekst); /* Czekaj na zakonczenie watku "drugi" */ printf("id drugiego: %u\n",drugi_id); return 0; } Przekazywanie argumentu tekstowego do wątku poprzez argument arg. J Argument: Jakis tam tekst Drugi: 2 id drugiego: 2 tu: przekazanie id wątku głównego
17
return NULL; lub pthread_exit(NULL); Zakończenie wątku
18
Funkcją pthread_join() pthread_join(id,&retval) powoduje wstrzymanie programu (wątku macierzystego) aż do momentu zakończenia wątku posiadającego id. Dołączanie wątku (join) Wymagana często jego obsługa, bo co będzie jeśli wątek główny zakończy swoje działanie a potomny nadal będzie musiał działać?
19
void* drugi(void* arg) { int i; for(i=0;i<4;i++) { printf("Drugi i: %d\n",i); sleep(1); } return NULL; } Dłuższe działanie procedury obsługi wątku … pthread_create(&drugi_id, &attr, drugi, "10"); pthread_join(drugi_id, &retval); printf("Koniec \n"); return 0; } Drugi i: 0 Drugi i: 1 Drugi i: 2 Drugi i: 3 Koniec Drugi i: 0 Koniec … pthread_create(&drugi_id, &attr, drugi, "10"); printf("Koniec \n"); return 0; bez join niedokończony wątek
20
void* drugi(void* arg) { int i; for(i=0;i<4;i++) { printf("Drugi i: %d\n",i); sleep(1); } return NULL; } Dłuższe działanie procedury obsługi wątku pthread_create (&drugi_id, &attr, drugi, "10"); printf("AKCJA 1\n"); pthread_join(drugi_id, NULL); printf("AKCJA 2\n"); printf("AKCJA 3\n"); return 0; Drugi i: 0 AKCJA 1 Drugi i: 1 Drugi i: 2 Drugi i: 3 AKCJA 2 AKCJA 3 Drugi i: 0 AKCJA 1 AKCJA 2 Drugi i: 1 Drugi i: 2 Drugi i: 3 AKCJA 3 pthread_create (&drugi_id, &attr, drugi, "10"); printf("AKCJA 1\n"); printf("AKCJA 2\n"); pthread_join(drugi_id, NULL); printf("AKCJA 3\n"); return 0; WYBÓR MIEJSCA DOŁĄCZENIA
21
Wątek można odłączyć. Po zakończeniu wątku pewne jego zasoby są wciąż przechowywane, aż do momentu dołączenia wątku funkcją pthread_join(). Wątek odłączony zwalnia swoje zasoby zaraz po zakończeniu pracy. Nie ma potrzeby (ani możliwości) oczekiwania (join) na odłączony wątek. Wszelka informacja związana z wątkiem znika w chwili zakończenia jego pracy. Wątek odłącza się funkcją pthread_detach(): pthread_detach(id) Odłączanie wątku (detach)
22
void* drugi(void* arg) { printf("Drugi\n"); return NULL; } Procedura obsługi wątku pthread_create(&drugi_id, &attr, drugi, NULL); int i; for(i=0;i<4;i++) { printf("Glowny i: %d\n",i); sleep(1); } printf("drugi_id:%d\n",drugi_id); pthread_detach(drugi_id); printf("AKCJA \n"); pthread_create(&drugi_id2, &attr,drugi,NULL ); printf("drugi_id2:%d\n",drugi_id2); Drugi Glowny i: 0 Glowny i: 1 Glowny i: 2 Glowny i: 3 drugi_id:2 AKCJA Drugi drugi_id2:2 Drugi Glowny i: 0 Glowny i: 1 Glowny i: 2 Glowny i: 3 drugi_id:2 AKCJA Drugi drugi_id2:3 pthread_create(&drugi_id, &attr, drugi, NULL); int i; for(i=0;i<4;i++) { printf("Glowny i: %d\n",i); sleep(1); } printf("drugi_id:%d\n",drugi_id); printf("AKCJA \n"); pthread_create(&drugi_id2, &attr, drugi, NULL ); printf("drugi_id2:%d\n",drugi_id2); DETACH bez detach – mimo że wątek zakończył działanie to pamięta swoje id
23
Współbieżna praca dwóch wątków #include void* drugi(void* arg) { int i; pthread_t id; id = pthread_self(); for( i=0; i<10; i++) { printf("\t\t\tDrugi: %ld\n",(long int)id); sleep(1); } return NULL; } int main(int argc, char* argv[]) { int i; /* Zmienna licznikowa dla petli */ pthread_t id; /* Identyfikator tego watku*/ pthread_t drugi_id; /* identyfikator tworzonego watku */ pthread_attr_t attr; /* Atrybuty watku */ pthread_attr_init( &attr ); pthread_create(&drugi_id, &attr, drugi, NULL); id = pthread_self(); for( i=0; i<5; i++) { printf("Glowny: %ld\n",(long int)id); sleep(2); } pthread_join(drugi_id, NULL); return 0; } Drugi: 2 Glowny: 1 Drugi: 2 Glowny: 1 Drugi: 2 Glowny: 1 Drugi: 2 Glowny: 1 Drugi: 2 Glowny: 1 Drugi: 2
24
Problemy…. parallel slowdown Podczas wykonywania programu równoległego, poszczególne jego zadania składowe mogą operować na wspólnych zasobach Nie każda próba wykonania równoległego daje przyspieszenie obliczeń. Jeśli program jest dzielony na wiele podzadań, to w pewnym momencie problemy komunikacji zaczynają przeważać nad zyskiem ze "zrównoleglenia". Pomimo zwiększania teoretycznej mocy obliczeniowej może dojść do spowolnienia obliczeń. Zjawisko to nazywane jest spowolnieniem równoległym (ang. parallel slowdown).
25
Do poprawnej współpracy wątków często potrzebna jest synchronizacja ich pracy. W programowaniu wielowątkowym stosuje się w tym celu mechanizmy synchronizacji. Mechanizmy są używane w celu: Synchronizacja zapewnienia prawidłowych zależności czasowych przy współbieżnym wykonywaniu algorytmów, zapewnienia spójnego dostępu do danych.
26
…ponieważ… Np. podzadanie 1 ma N instrukcji, a podzadanie 2 ma M instrukcji. Zadania są wykonywane współbieżnie i kolejność czasowa wykonywania k-tej instrukcji zadania 1 i i-tej instrukcji zadania 2 może być przypadkowa – inna dla różnych wykonań programu (decydują o tym stany zasobów, sygnały zewnętrzne itp.) Musimy zatem zapewnić sytuację, że ZAWSZE wyniki będą PRAWIDŁOWE
27
Wątek może zmienić za wcześnie dane, które potrzebne są innemu wątkowi w stanie poprzednim Wątek może za późno obliczyć dane potrzebne innemu wątkowi Główne błędy przy braku synchronizacji
28
Wątek AWątek B Podstaw wartość x do Ndodaj 1 do N Podstaw wartość y do Mdodaj 1 do M Sprawdź, czy M jest większe lub równe N decydująca jest kolejność wykonywania instrukcji za wspólnych danych co może powodować błędy !
29
Brak synchronizacji oblicz B akcja Wątek główny Wątek drugi C=A+B oblicz A czas B nieznane
30
C= A+B A=1 B=1 Wątek główny Wątek drugi B=2 czas modyfikacja B Problem pojawia się przy dostępie do wspólnych danych, w momencie gdy dwa wątki próbują modyfikować dane "jednocześnie". Nieprzewidywalne rezultaty (nieprawidłowy wynik obliczeń). Race condition Wyścig Wyścigi i wzajemne wykluczanie Konstrukcje w aplikacji współbieżnej powodujące, że wynik aplikacji w sposób przypadkowy zależy od czasu wykonania pewnych ciągów instrukcji Wykluczanie – sytuacja, gdy dwa podzadania (ich fragmenty) nie mogą być wykonywane równocześnie
31
#include #define ILE_WATKOW 8 #define LIMIT 10000000 pthread_t tid[ILE_WATKOW]; static int x; void * kod (void *arg) { int numer=(int)arg; if (numer%2==0) { while (x<LIMIT) x++; } else { while (x>0) x--; } printf("Na mecie: %d x: %d \n", numer, x); return (void*) numer; } int main (int argc, char *argv[]) { int i, status, num; num = LIMIT/2; //4000000 for (i=0; i< ILE_WATKOW; i++) pthread_create (&tid[i], NULL, kod, (void *)(i+1)); for (i=0; i< ILE_WATKOW; i++) pthread_join (tid[i], NULL); printf("KONIEC !!! x: %d\n",x); return (0); } Na mecie: 1 x: 0 Na mecie: 2 x: 10000000 Na mecie: 3 x: 0 Na mecie: 4 x: 10000000 Na mecie: 5 x: 0 Na mecie: 6 x: 10000000 Na mecie: 7 x: 0 Na mecie: 8 x: 10000000 KONIEC !!! x: 10000000 8 wątków "na wyścigi" zwiększa w pętli lub zmniejsza liczbę (zależy od numeru wątku – parzysty/nieparzysty)
32
… chroniona zmienna, która ustanawia metodę kontroli dostępu przez wiele procesów/wątków do wspólnego zasobu Semafory po raz pierwszy opisane przez Edsgera Dijkstrę Semafor Semafory binarne i zliczające Semafory zliczające – problem stolików w restauracji liczba_stołów liczba_wolnych po zajęciu miejsca (jeśli liczba_wolnych>0) liczba_wolnych - - po zwolnieniu stołu liczba_wolnych++ liczba_wolnych=liczba_stołów i brak chętnych - oczekiwanie lub koniec WYKLUCZANIE – nie można posadzić dwóch osób na tym samym miejscu!
33
Mutex (Mutual Exclusion) jest mechanizmem synchronizacji, który umożliwia zagwarantowanie wyłączności w dostępie do danych. Stosując mutex można zabezpieczyć się przed problemem race condition. Mutex jest obiektem, który może być w jednym z dwu stanów: odblokowany (unlocked) zablokowany (locked) Dwie podstawowe operacje, które są przeprowadzane na muteksach to: blokowanie (lock) odblokowywanie (unlock) Mutex to szczególny przypadek semaforów binarnego i ogólnego
34
… to wymaganie, aby ciąg operacji na zasobie (pamięci) był wykonany w trybie wyłącznym – tylko przez jeden z wątków (procesów) Wzajemne wykluczanie
35
Podzadanie APodzadanie B 1A: Zablokuj zmienną V1B: Zablokuj zmienną V 2A: Odczytaj zmienną V2B: Odczytaj zmienną V 3A: Dodaj 1 do zmiennej V3B: Dodaj 3 do zmiennej V 4A: Zapisz wartość w zmiennej V4B: Zapisz wartość w zmiennej V 5A: Odblokuj zmienną V5B: Odblokuj zmienną V Tylko jedno z dwóch zadań z sukcesem zablokuje zmienną V i uzyska do niej wyłączny dostęp, podczas gdy drugie będzie musiało czekać na jej odblokowanie. Zastosowanie powyższej konstrukcji daje gwarancję poprawnego wykonania programu, kosztem jest jednak jego spowolnienie, które może być znaczne.
36
Blokuj MUTEX Odblokuj MUTEX B=1 C=A+B Sekcja krytyczna
37
założyć, że na odcinku drogi może przebywać jednocześnie nieokreślona liczba pojazdów (zależąca od ich prędkości) Semafory i muteksy Implementacja: Stan świateł:
38
Semafory są obiektami globalnymi. Dowolny proces może je "opuszczać" albo "podnosić". Umożliwia to powstanie sytuacji, w której jedno z zadań wykona operację CZEKAJ aby synchronizować dostęp do jakiegoś zasobu, a później inne, niezwiązane logicznie z tym zasobem, podniesie semafor. Może to prowadzić do uszkodzenia wspólnych danych, a nawet do załamania całego systemu. Dlatego też, wiele implementacji wprowadza pewne cechy dla muteksów. Jedną z nich jest zasada posiadania. Otóż polega ona na tym, że jeśli jakieś zadanie zablokuje muteks (nada mu wartość 1), to tylko ono może ten muteks odblokować (nadać mu wartość 0). Takie podejście eliminuje potencjalny problem niespójności danych.
39
Blokowanie wielu zmiennych przy użyciu nieatomowych blokad może spowodować zakleszczenie. Atomowość blokady to własność, która gwarantuje, że wszystkie zmienne blokowane są razem, to znaczy jeśli dwa podzadania próbują zablokować kilka zmiennych, to uda się to tylko jednemu z nich i blokada powstanie na wszystkich zmiennych. Jeśli natomiast blokady nie są atomowe, to może się zdarzyć, że jeśli dwa podzadania próbują zablokować te same dwie zmienne, to jedno z nich zablokuje jedną a drugie drugą. Wówczas oba podzadania czekają na siebie nawzajem i żaden z nich nie może zakończyć działania. Taką sytuację nazywamy zakleszczeniem. Zakleszczenie
40
Dane wspólne dla wątków powinny być modyfikowane tylko wewnątrz sekcji krytycznych. Wtedy wątek ma gwarancję, że inny wątek nie zmodyfikuje mu danych skojarzonych z muteksem, ma wyłączność w dostępie do danych. Typ pthread_mutex_t reprezentujący mutex oraz zestaw funkcji operujących na muteksie. Istotne funkcje: pthread_mutex_lock() - blokowanie muteksa. Jeśli mutex jest zablokowany przez inny wątek, to bieżący wątek jest wstrzymywany aż do momentu odblokowania. Jeśli mutex jest odblokowany, to bieżący wątek blokuje go. pthread_mutex_unlock() - odblokowanie (zwolnienie) muteksa. Synchronizacja muteksem
41
#include int b; /* Dane wspolne */ void* drugi (void* arg) { printf("\t\t\t\t2: Pracuje...\n"); sleep(1); /* Cos tam robie 1 sekunde */ printf("\t\t\t\t2: Modyfikuje b\n"); b = 2; printf("\t\t\t\t2: b = %d\n", b); return NULL; } int main(int argc, char* argv[]) { pthread_t drugi_id; /* identyfikator watku */ pthread_attr_t attr; /* Atrybuty watku */ int a,c; /* Dane lokalne watku glownego */ pthread_attr_init( &attr ); pthread_create(&drugi_id, &attr, drugi, NULL); printf("1:Modyfikuje b\n"); a = 1; b = 1; /* Inicjalizuj "a" i "b" */ printf("1: a = %d, b = %d\n",a, b); /* Cos tam robimy... */ printf("1: Pracuje... \n"); sleep(2); /* Oblicz "c" i wydrukuj wynik */ c = a + b; printf("1: c = a + b = %d\n",c); if(c == 2) { printf("1: Obliczenia prawidlowe.\n"); } else { printf("1: Zle!\n"); } /* Czekaj na zakonczenie watku "drugi" */ pthread_join(drugi_id, NULL); return 0; } 2: Pracuje... 1:Modyfikuje b 1: a = 1, b = 1 1: Pracuje... 2: Modyfikuje b 2: b = 2 1: c = a + b = 3 1: Zle! bez blokady
42
#include int b; /* Dane wspolne */ /* Mutex chroniacy dane wspolne */ pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER; /* Procedura wykonywana przez watek */ void* drugi (void* arg) { printf("\t\t\t\t2: Pracuje...\n"); sleep(1); /* Cos tam robie przez sekunde */ /*Blokuj mutex */ pthread_mutex_lock(&mutex_b); printf("\t\t\t\t2: Modyfikuje b\n"); b = 2; printf("\t\t\t\t2: b = %d\n", b); pthread_mutex_unlock(&mutex_b); /* Odblokuj*/ return NULL; } int main (int argc, char* argv[]) { pthread_t drugi_id; /* identyfikator tworzonego watku */ pthread_attr_t attr; /* Atrybuty watku */ int a,c; /* Dane lokalne watku glownego */ pthread_attr_init( &attr ); pthread_create(&drugi_id, &attr, drugi, NULL); /* Inicjalizuj "a" */ printf("1:Modyfikuje b\n"); a = 1; pthread_mutex_lock(&mutex_b); /* Blokuj mutex */ b = 1; printf("1: a = %d, b = %d\n",a, b); printf("1: Pracuje... \n"); /* Cos tam robimy... */ sleep(2); c = a + b; /* Oblicz "c" */ pthread_mutex_unlock(&mutex_b); /* Odblokuj mutex */ printf("1: c = a + b = %d\n",c); /* Drukuj wynik */ if(c == 2) { printf("1: Obliczenia przeprowadzone prawidlowo.\n"); } else { printf("1: Zle!!!!\n"); } /* Czekaj na zakonczenie watku */ pthread_join(drugi_id, NULL); return 0; } 2: Pracuje... 1:Modyfikuje b 1: a = 1, b = 1 1: Pracuje... 1: c = a + b = 2 1: Obliczenia przeprowadzone prawidlowo. 2: Modyfikuje b 2: b = 2 Prawidłowo z muteksem
Podobne prezentacje
© 2024 SlidePlayer.pl Inc.
All rights reserved.