V prípade, že vo viacerých vláknach bežiacich súčasne používame rovnaké prostriedky OS - pridelenú pamäť (vrátane spoločných premenných), súbory, atď. je potrebné prístup k nim synchronizovať - t.j. zabezpečiť aby daný prostriedok v danom čase nemodifikovalo viac ako jedno vlákno.
Medzi základné prostriedky na synchronizáciu implementované knižnicou
pthreads patria mutexy. Mutex
je synchronizačný prostriedok, ktorý pri správnom
používaní zabezpečuje to, že určitú časť kódu (takú
kde sa pracuje so zdieľaným prostriedkom) v danom čase vykonáva len
jedno vlákno. Ak by iné vlákna chceli pristupovať k tomu istému
prostriedku musia čakať.
Mutex je objekt, ktorý môže byť v dvoch stavoch:
a sú nad ním implementované dve atomické operácie (také, že sa vykonajú ako celok bez prerušenia):
Mutexy sa najčastejšie používajú tak, že ku každému samostatnému zdieľanému prostriedku je pridelený jeden mutex. Ak chce vlákno vykonávať akýkoľvek kód, ktorý manipuluje so zdieľaným prostriedkom, musí na mutexe, ktorý je pridelený k tomuto prostriedku zavolať operáciu lock. Akonáhle vlákno dokončí manipuláciu so zdieľaným prostriedkom, musí zavolať na príslušnom mutexe operáciu unlock.
V knižnici pthreads sú mutexy reprezentované typom
pthread_mutex_t.
Pred použitím mutexu je nutné ho inicializovať pomocou funkcie, pthread_mutex_init a keď už nie je potrebný upratať ho pomocou funkcie pthread_mutex_destroy.
Funkcia pthread_mutex_lock slúži na zamknutie a funkcia pthread_mutex_unlock na odomknutie mutexu.
Cvičenie V nasledujúcom príklade si vyskúšame synchronizovaný prístup k štandardnému výstupu z viacerých vlákien, ktoré naň budú paralelne zapisovať. Nechceme však aby výstup jednovlivých vlákien bol "pomiešaný", chceme aby vlákna vypísali vždy celé riadky.
Vytvorte zdrojový súbor
pthread_mutex.cs nasledovným obsahom:
#include <pthread.h> #include <sys/ioctl.h> #include <stdio.h> struct thread_data{ unsigned columns; unsigned lines; char output; pthread_mutex_t *stdout_mutex; }; void* thread_main(void* ptr) { struct thread_data* data = (struct thread_data*)ptr; unsigned l, c; for(l=0; l<data->lines; ++l) { pthread_mutex_lock(data->stdout_mutex);
for(c=0; c<data->columns; ++c) { fputc(data->output, stdout); } fputc('\n', stdout); pthread_mutex_unlock(data->stdout_mutex);
} return NULL; } unsigned get_term_width(void)
{ struct winsize ws; ioctl(0, TIOCGWINSZ, &ws); return ws.ws_col; } int main(void) { unsigned w = get_term_width(); pthread_mutex_t stdout_mutex; pthread_mutex_init(&stdout_mutex, NULL);
struct thread_data data[4] = { {w, 1000, 'X', &stdout_mutex}, {w, 1200, 'O', &stdout_mutex}, {w, 1500, 'I', &stdout_mutex}, {w, 1500, '.', &stdout_mutex} };
pthread_t threads[4]; int i; for(i=0; i<4; ++i) { pthread_create(&threads[i], NULL, &thread_main, &data[i]);
} for(i=0; i<4; ++i) { pthread_join(threads[i], NULL);
} pthread_mutex_destroy(&stdout_mutex);
return 0; }
|
Štruktúra, ktorá riadi výstup jednotlivých vlákien:
|
|
V tomto programe je zdieľaným prostriedkom štandardný výstup.
Ak chce niektoré z vlákien štandardný výstup používať, a
vypísať celý riadok bez prerušenia inými vláknami, musí k
nemu mať výhradný prístup. To zabezpečíme tak, že pred tým
ako začneme vypisovať riadok, zamkneme mutex, ktorý riadi prístup
k |
|
Akonáhle vypíšeme celý riadok, mutex odomkneme a tým dáme šancu jednému z ostatných vlákien čakajúcich na mutexe, na to aby mutex zamkli a pokračovali vo vykonávaní. |
|
Vráti šírku terminálu. |
|
Vytvoríme a zinicializujeme mutex. |
|
Vytvoríme dáta pre štyri vlákna, prvé bude vypisovať 1000 riadkov
pozostávajúcich zo znaku |
|
Vytvoríme a spustíme štyri vlákna, pričom každému dáme ukazovateľ na vlastnú inštanciu dát. |
|
Počkáme na dokončenie všetkých spustených vlákien. |
|
Mutex už nebudeme potrebovať, takže ho môžme "upratať". |