Istovremeni pristup podacima od strane više dretvi se ponekad ne smije dopustiti. To su situacije u kojima dretve koriste zajedničke varijable, a čija bi istovremena uporaba izazvala greške. Takvi se kritični odsječci programa zaštićuju međusobnim isključivanjem. Kod korištenja zajedničkih podataka treba također uzeti u obzir da promjena koju uzrokuje jedna dretva ne mora baš istog trenutka biti vidljiva svim ostalim dretvama. Odnosno, zbog toga što procesori koriste vlastite priručne spremnike, promjena podatka se privremeno može obaviti samo lokalno, a ažuriranje glavnog spremnika može se obaviti tek kasnije. Takvi se dijelovi programa također zaštićuju.
UNIX operacijski sustav (SUN Solaris, a i Linux) omogućuje sljedeće načine sinkronizacije među dretvama: semafori, međusobno isključivanje, uvjetne varijable.
Semafori se obično upotrebljavaju za pristup ograničenim
sredstvima od strane većeg broja korisnika (dretvi), tj. služe kao brojači događaja. Osnovne funkcije za rad sa
semaforima u višedretvenom programu su:
sem_init, sem_post
i
sem_wait
.
int sem_init(sem_t *sem, int mprocesi, unsigned int koliko);
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
sem
je pokazivač na
varijablu semafora, koliko
je broj na
koji se postavi semafor, mprocesi
označava je li semafor predviđen za
sinkronizaciju dretvi istog procesa (0) ili dretvi različitih procesa (nešto
različito od nule).
Ukoliko se ove funkcije koriste za sinkronizaciju procesa, objekt semafora (ono na što pokazuje sem) mora biti u zajedničkoj memoriji (shmget+shmat+...)!
Funkcija sem_post
jedinično povećava semafor.
Funkcija sem_wait
smanjuje vrijednost semafora za jedan, ako je vrijednost semafora veća od nule, u protivnom čeka dok se vrijednost ne poveća.
sem_t *sem; //globalna varijabla = za kazaljku na objekt u zajedničkoj memoriji void proces (int id) { ... sem_wait (sem); i/ili sem_post (sem); ... } int main() { ... ID = shmget (IPC_PRIVATE, sizeof(sem_t), 0600); sem = shmat (ID, NULL, 0); shmctl (ID, IPC_RMID, NULL); //moze odmah ovdje, nakon shmat, ili na kraju nakon shmdt jer IPC_RMID oznacava da segment treba izbrisati nakon sto se zadnji proces odijeli od tog segmenta (detach) sem_init (sem, 1, 5); //početna vrijednost = 5, 1=>za procese ... fork () ... ... ... wait (NULL) ... sem_destroy (sem); shmdt (sem); return 0; }Kada bi se semafor koristio za dretve unutar istog procesa, onda bi
sem
mogao biti obična globalna varijable (sem_t sem;
), ne bi bilo potrebe rezervirati memoriju za njega, a su svim ostalim funkcijama bi se pozivao preko adrese (npr. sem_init(&sem, 1, 5);
).
pthread_mutex_init
, zaključavanje funkcijom
pthread_mutex_lock
, a otključavanje funkcijom
pthread_mutex_unlock
(Solaris ima i svoje vlastite funkcije za te
operacije koje nemaju prefix "pthread_")
int pthread_mutex_init(pthread_mutex_t
*ključ, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *ključ);
int pthread_mutex_unlock(pthread_mutex_t *ključ);
ključ
pokazuje na varijablu zaključavanja, attr
definira karakteristike stvorene
varijable (NULL za pretpostavljene vrijednosti). Sve dretve koje
pokušaju zaključati već zaključanu varijablu ostaju blokirane na pozivu sve dok
varijabla ostaje zaključana. Kada se varijabla otključa, tada samo jedna dretva
ulazi u kritični osječak, uz ponovno zaključavanje varijable. Zaključavanjem
se smanjuje moguća istovremenost u izvođenju skupine dretvi, te je njihova
upotreba prihvatljiva (obavezna) samo u kritičnim odsječcima.
Uvjetne varijable se koriste kada želimo da neka dretva zaustavi svoje izvođenje te čeka da se određeni uvjet ispuni, tj. da ga ispuni neka druga dretva. Funkcijama za korištenje uvjetnih varijabli obavezno prethodi zaključavanje, budući su uvjetne varijable globalne, tj. zajedničke cijelom procesu.
Osnovne funkcije za rukovanje uvjetnim varijablama su
pthread_cond_init, pthread_cond_wait, pthread_cond_signal
i
pthread_cond_broadcast.
int pthread_cond_init(pthread_cond_t
*uvjet, const pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *uvjet, pthread_mutex_t *ključ);
int pthread_cond_signal(pthread_cond_t *uvjet);
int pthread_cond_broadcast(pthread_cond_t *uvjet);
uvjet
je kazaljka na uvjetnu varijablu,
ključ
jest kazaljka na varijablu
zaključavanja, attr
odefinira
karakteristike stvorene varijable (NULL za pretpostavljene vrijednosti). Pozivom
pthread_cond_wait
pozivajuća dretva se
postavlja u stanje čekanja tj. premješta ju se u red
uvjet
i istovremeno se otljučava
ključ
. Čekanje te dretve završava neka druga dretva pozivima
pthread_cond_signal
ili
pthread_cond_broadcast
. Po primitku "signala"
dretva prije nastavka rada najprije mora ponovno zaključati
ključ
. Poziv
pthread_cond_signal
ispunjuje uvjet za
nastavaka samo jedne dretve iz reda čekanja na istu uvjetnu varijablu, dok poziv
pthread_cond_broadcast
omogućuje svim takvim
dretvama nastavak izvođenja. Ako niti jedna dretva nije bila blokirana na
uvjetnoj varijabli prilikom poziva pthread_cond_signal
i pthread_cond_broadcast
, onda ovi pozivi
nemaju nikakav učinak, tj. ako već u slijedećem trenutku neka dretva pozove
pthread_cond_wait
ona ostaje blokirana.
Uvjetnim varijablama moguće je ostvariti sustav
monitora - mehanizma kojim je moguće istovremeno provjeriti više uvjeta (zauzeti
resurse) potrebnih za napredovanje dretve.
Monitor se sastoji od skupa procedura i strukture podataka nad kojima procedure djeluju i koje nisu vidljive izvan monitora. Procedure se ne smiju izvoditi paralelno. Unutar monitora ispituje se uvjet koji utječe na odvijanje zadatka. Ukoliko je uvjet ispunjen, zadatak zauzima sredstva i obavlja potrebne akcije nad njima unutar monitora. Po završetku oslobađa sredstva te dozvoljava drugom zadatku ulazak u monitor te izlazi iz monitora. Ukoliko po ulasku u monitor uvjet nije ispunjen tada zadatak odlazi u red čekanja na taj uvjet, tj. prividno napušta monitor.
Ostvarenje monitora u višedretvenom programu prilično je jednostavna budući
na raspolaganju stoje funkcije međusobnog isključavanja čime se jednostavno
postiže da samo jedna dretva ulazi u monitor, te funkcije za rukovanje
uvjetnim varijablama koje služe za ostvarenje reda uvjeta. Ostvarenje monitora koji zauzima sredstva
p
i q
(npr. lijevi i desni štapić kod problema pet filozofa) prikazan je u nastavku:
void uzmi_sredstva (int p, int q) { pthread_mutex_lock (&monitor); while ( p == 0 || q == 0 ) pthread_cond_wait (&uvjet,&monitor); p = q = 0; pthread_mutex_unlock (&monitor); }
void vrati_sredstva (int p, int q) { pthread_mutex_lock (&monitor); p = q = 1; pthread_cond_broadcast (&uvjet); pthread_mutex_unlock (&monitor); }
... pthread_mutex_t m; pthread_cond_t red; ... void *dretva (void *p) { ... pthread_mutex_lock (&m); ... while ( uvjet-blokiranja ) pthread_cond_wait (&red, &m); ... pthread_mutex_unlock (&m); ... pthread_mutex_lock (&m); ... if ( uvjet-otpuštanja ) pthread_cond_signal (&red); ili pthread_cond_broadcast (&red); ... pthread_mutex_unlock (&m); ... } int main () { ... pthread_mutex_init (&m, NULL); pthread_cond_init (&red, NULL); ... pthread_create (..., ..., dretva, ...); ... pthread_join (...); ... return 0; }
Stranice (manual) POSIX dretvi u kojima su detaljno opisane
funkcije za rad s dretvama pthread:
pthread,
pthread_create,
pthread_exit,
pthread_detach,
pthread_join,
pthread_mutex_init,
pthread_mutex_lock,
pthread_mutex_unlock,
pthread_mutex_destroy,
pthread_cond_init,
pthread_cond_wait,
pthread_cond_signal,
sem_init, sem_wait,
sem_post, sem_destroy...