Dagens tema: Synkronisering Kappløp og kritiske regioner Blokkering Produsent/konsument-problemet Semaforer Fraktaleksemplet med semaforer og delt lager. «De spisende filosofer»: 3 gale løsninger En foreløbig oversikt Ark 1 av 28 Forelesning 5.5.1999
Kappløp Ved parallellprogrammering kan det oppstå en situasjon med kappløp («race condition»). Dette innebærer at resultatet av en kjøring avhenger av hvilken rekkefølge prosessene kjøres i og hvor fort de går i forhold til hverandre. Slike kappløp ønsker vi vanligvis ikke. Et typisk eksempel på kappløp er forrige ukes forsøk på å bruke felles lager til fraktalprogrammet: Ved å benytte synkronisering kan vi unngå kappløp. Forelesning 5.5.1999 Ark 2 av 28
Teknikker brukt hittil Teknikk Synk Data Spesielt UNIX filer UNIX rør Forutsetter samme urprosess. Kan ikke alltid brukes. Delt lager Forelesning 5.5.1999 Ark 3 av 28
Kritiske regioner Vanligvis kan flere prosesser kjøre uavhengig av hverandre lenge av gangen, men noen ganger kan de «tråkke hverandre på tærne» fordi de samtidig aksesserer en felles ressurs (vanligvis et delt lager). De delene av programmene hvor dette kan skje, kalles kritiske regioner. Når man skriver parallelle programmerer, er det viktig å finne de kritiske regionene og foreta seg noe spesielt der. I fraktalprogrammet var bruk av det felles lageret en kritisk region. Forelesning 5.5.1999 Ark 4 av 28
Beskyttelse av kritiske regioner Forskning har påpekt at for å skrive gode parallelle programmer som unngår kappløp, må koden oppfylle følgende fire krav: 1. Det må aldri være mer enn én prosess av gangen inne i hver kritiske region. 2. Prosessene må ikke forutsette noe om hverandres eksekveringshastighet. 3. En prosess utenfor kritisk region må ikke hindre en annen prosess å gå inn der. 4. Ingen prosess må vente uendelig lenge før den kan komme inn i kritisk region. (Dette kalles utsulting («starvation»).) Dette skal vi se på ulike teknikker for å oppnå. Forelesning 5.5.1999 Ark 5 av 28
Venting Noen teknikker er basert på venting, for eksempel int lock = 0;. while (lock) ; lock = 1; kritisk region lock = 0; Dette har to ulemper: Aktiv venting kaster bort mye tid. Flere prosesser kan stjele låsen samtidig. Venting bør kun brukes til å implementere bedre tekniker. Forelesning 5.5.1999 Ark 6 av 28
Blokkering Alle gode løsninger for synkronisering er basert på å la operativsystemet blokkere prosesser som ikke skal kjøre. Prosesser kan være i følgende tilstander: ' Blokkert & 1 '? ' & $ % Aktiv & Følgende overganger finnes: 6 4 3 - $ % ' & $ 2 $? Klar $ % 1. En prosess blokkeres (f eks fordi den venter på data). 2. Fordeleren velger en annen prosess. 3. Fordeleren velger denne prosessen til å kjøre. 4. En blokkert prosess får det den venter på. Forelesning 5.5.1999 Ark 7 av 28
Produsent/konsument-problemet Dette problemet er et av de aller mest kjente problemene innen parallellstyring. '$ &% P - Buffer - '$ &% K I dette systemet er det to prosesser: P produserer ett eller annet. Dette legges i bufferen. K konsumerer det P lager, dvs. henter det fra bufferen og gjør ett eller annet med det. Siden bufferen er begrenset, hender det at P eller K må ta en pause: P må stoppe hvis det ikke er mer ledig plass i bufferen. K må stoppe hvis det ikke er flere elementer å hente i bufferen. Dessuten må ikke mer enn én prosess av gangen aksessere bufferen. Forelesning 5.5.1999 Ark 8 av 28
Semaforer Semaforer ble oppfunnet i 1965 av Edsger Dijkstra og var den første praktisk brukbare mekanismen for parallellstyring. En semafor er en datastruktur: Verdi Kø - - - Semaforer har to operasjoner: down(&s) Hvis verdien til s er > 0, vil den bli senket med 1. Hvis ikke, vil prosessen bli blokkert og dens nummer lagt til køen til s. up(&s) Hvis køen til s er tom, økes verdien med 1. Hvis ikke, fjernes ett prosessnummer fra køen, og den tilhørende prosessen startes opp igjen. Forelesning 5.5.1999 Ark 9 av 28
Semaforer beskytter kritisk region semaphore mutex = 1; /* Mutual exclusion */. down(&mutex); Kritisk region up(&mutex); Med en slik beskyttelse er det garantert at maksimalt én prosess av gangen er i kritisk region. En semafor som kun har verdiene 0 og 1, kalles en binær-semafor. Forelesning 5.5.1999 Ark 10 av 28
Bruk av semaforer Hva skjer når en prosess vil inn i kritisk region? Hvis mutex.verdi> 0, er det bare å gå inn. Hvis mutex.verdi= 0, må prosessen vente. Ved å sette en annen initialverdi, kan man tillate flere inne i kritisk region samtidig. Forelesning 5.5.1999 Ark 11 av 28
PK-problemet løst med semaforer #define N 100 semaphore mutex = 1; semaphore empty = N; /* Teller tomme */ semaphore full = 0; /* Teller fulle */ producer() { int item; while (TRUE) { produce_item(&item); down(&empty); down(&mutex); enter_item(item); up(&mutex); up(&full); consumer() { int item; while (TRUE) { down(&full); down(&mutex); remove_item(&item); up(&mutex); up(&empty); consume_item(item); Forelesning 5.5.1999 Ark 12 av 28
Her skjer følgende: Det å sette verdier inn i bufferen (ved å kalle på funksjonen enter item) og ta dem ut derfra (ved å kalle remove item) er kritiske regioner. Disse beskyttes av en binær-semafor (mutex). For å blokkere producer når bufferen er full, innføres semaforen empty, som teller hvor lange tomme plasser det er i bufferen. Hvis det ikke er noen ledige plasser, vil producer bli blokkert. Når consumer harhentetetnyttelement fra bufferen, vil den øke empty. Hvis producer da var blokkert, vil den bli frigjort. For å blokkere consumer når bufferen er tom, innføres semaforen full, som teller hvor mange fulle posisjoner det er i bufferen. Hvis det ikke er noen fulle posisjoner, vil consumer bli blokkert. Når producer har lagt et nytt element i bufferen, vil den øke full. Hvis consumer da var blokkert, vil den bli frigjort. Forelesning 5.5.1999 Ark 13 av 28
Semaforer i SVIPC i UNIX For å kunne operere på semaforer i UNIX trenger vi et par definisjonsfiler: #include <sys/ipc.h> #include <sys/sem.h> En gruppe på n semaforer allokeres slik i SVIPC: int sem_id; sem_id = semget(ipc_private, /* Nøkkel */ n, /* Antall */ 0700); /* Aksesskode */ if (sem_id<0) perror("semprog"); Etter bruk må man huske å frigjøre semaforene: semctl(sem_id, 0, IPC_RMID); Forelesning 5.5.1999 Ark 14 av 28
Operasjonene down og up kan programmeres slik (uten at man trenger å forstå det): void down(int n) { struct sembuf op; int status; op.sem_num = n; op.sem_op = -1; op.sem_flg = 0; status = semop(sem_id, &op, 1); if (status!= 0) {{ printf("semaphore down error %d.\n", status); exit(1); void up(int n) { struct sembuf op; int status; op.sem_num = n; op.sem_op = 1; op.sem_flg = 0; status = semop(sem_id, &op, 1); if (status!= 0) {{ printf("semaphore up error %d.\n", status); exit(1); Forelesning 5.5.1999 Ark 15 av 28
Fraktal-eksemplet med semaforer Fraktal-eksemplet vårt er et meget godt eksempel på produsent-konsument-problemet: Én eller flere produsenter produserer kolonner med fraktalpunkter. Bufferen har plass til ett sett data. En konsument mottar kolonnene med data og tegner dem på skjermen. Dataoverføringen skjer via delt lager. Forelesning 5.5.1999 Ark 16 av 28
Deklarasjoner #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/types.h> #include <stdio.h> #include <time.h> #include <unistd.h> #include "fraktal.h" #include "x147.h" extern pid_t safefork(void); #define NX 1000 #define NY 800 #define MAX 180 #define MUTEX 0 /* Våre tre semaforer */ #define FULL 1 #define EMPTY 2 float x1 = -0.01, x2 = 0.01, y1 = -0.01, y2 = 0.01; typedef struct { int xpos; unsigned char ycol[ny]; col_data; int sh_mem_id, sem_id; col_data *sh_mem; Forelesning 5.5.1999 Ark 17 av 28
Hovedprogrammet Hovedprogrammet allokerer ressurer og starter produsentene og konsumenten. Etterpå frigjøres ressursene. int main(int argc, char *argv[]) { time_t start_tid = time(null); int n_proc, nc; n_proc= argc <= 1? 1 : atoi(argv[1]); sh_mem_id = shmget(ipc_private,sizeof(col_data),0700); if (sh_mem_id < 0) perror("fraksem1"); sh_mem = shmat(sh_mem_id,0,0); sem_id = semget(ipc_private, 3, 0700); if (sem_id < 0) perror("fraksem1"); up(mutex); up(empty); /* Sett initialverdi. */ x147open(nx, NY); for (nc = 1; nc <= n_proc; ++nc) { if(safefork()==0){ produsent(nx*(nc-1)/n_proc, NX*nc/n_proc-1); exit(0); konsument(); printf("det hele tok %d sekunder.\n", time(null)-start_tid); x147pause(); shmctl(sh_mem_id, IPC_RMID, 0); semctl(sem_id, 0, IPC_RMID); Forelesning 5.5.1999 Ark 18 av 28
Produsentene Hver produsent skal generere data for sitt område av bildet. Når én kolonne er ferdig, sendes den til konsumenten. void produsent(int xstart, int xend) { float x, y; int ix, iy, fx; col_data buf; for (ix = xstart; ix <= xend; ++ix) { buf.xpos = ix; x = x1 + ix*(x2-x1)/(nx-1); for (iy = 0; iy < NY; ++iy) { y = y1 + iy*(y2-y1)/(ny-1); buf.ycol[iy] = fraktal(x, y, 255.0, MAX); /* Kritisk region... */ down(empty); down(mutex); *sh_mem = buf; up(mutex); up(full); /*... slutt på kritisk region. */ Forelesning 5.5.1999 Ark 19 av 28
Konsumenten Produsenten skal hente data fra produsentene og tegne dem på skjermen. Den er ferdig når alle kolonnene er tegnet opp. void konsument(void) { col_data buf; int cx, iy; for (cx = 0; cx < NX; ++cx) { /* Kritisk region... */ down(full); down(mutex); buf = *sh_mem; up(mutex); up(empty); /*... slutt på kritisk region. */ for (iy = 0; iy < NY; ++iy) { x147plot(buf.xpos, iy, buf.ycol[iy]); x147sync(); Ant prosesser oin modsognir 1 55 s 46 s 2 55 s 29 s 3 56 s 25 s 4 57 s 25 s 5 56 s 25 s 6 58 s 26 s Forelesning 5.5.1999 Ark 20 av 28
Husk! Husk at både semaforer og delt lager er en meget begrenset ressurs, og at disse ikke fjernes automatisk. Sjekking Kommandoen ipcs forteller om det er allokert noen semaforer eller delt lager: % ipcs IPC status from /dev/kmem as of Mon Apr 22 1999 T ID KEY MODE OWNER GROUP Message Queues: Shared Memory: m 0 0x53637444 --rw-r--r-- root wheel m 7901 0x00000000 --rw------- dag dag m 102 0x00000000 --rw------- dag dag m 3 0x00000000 --rw------- dag dag Semaphores: s 0 0x00000000 --ra------- dag dag s 1 0x00000000 --ra------- dag dag s 2 0x00000000 --ra------- dag dag Forelesning 5.5.1999 Ark 21 av 28
Fjerning Kommandoen ipcrm brukes til fjerning av både semaforer (opsjonen -s) og delt lager (opsjonen -m): % ipcrm -m 7901 % ipcrm -m 102 % ipcrm -m 3 % ipcrm -s 0 % ipcrm -s 1 % ipcrm -s 2 % ipcs IPC status from /dev/kmem as of Mon Apr 21 1997 T ID KEY MODE OWNER GROUP Message Queues: Shared Memory: m 0 0x53637444 --rw-r--r-- root wheel Semaphores: Forelesning 5.5.1999 Ark 22 av 28
«De spisende filosofer» Problemet med «the dining philosophers» er oppfunnet av Edsger Dijkstra i 1965. Rundt et bord sitter 5 filosofer: '$ - ~ ~ &%'$?&% ~ &% '$ '$ 6 ~ ~ &% 6 &% Hver av dem har foran seg en tallerken med spagetti og en gaffel til venstre for tallerkenen. Man trenger to gafler (ens egen og høyre sidemanns) for å spise spagetti. Hver filosof tilbringer dagen med vekselvis å tenke og å spise. De snakker aldri sammen. Problemet er å finne en algoritme som sørger for at alle filosofene får mat fra tid til annen. Forelesning 5.5.1999 Ark 23 av 28
Forsøk på løsning #define N 5 #define LEFT(x) ((x)==0? N-1 : (x)-1) #define RIGHT(x) ((x)==n-1? 0 : (x)+1) philosopher(i) /* Hva skal filosof i gjøre? */ int i; { while (TRUE) { think(); take_fork(i); take_fork(right(i)); eat(); put_fork(i); put_fork(right(i)); Rutinen take fork okkuperer angitte gaffel hvis den er ledig; hvis ikke, blir prosessen blokkert. Rutinen put fork frigjør angitte gaffel. Vurdering Kan føre til vranglås. Vi kan risikere at alle filosofene plukker opp sin gaffel og så sulter i hjel mens de venter på at en annen skal legge fra seg sin. Konklusjon: Ubrukelig. Forelesning 5.5.1999 Ark 24 av 28
Enda et forsøk Vi kan la en sulten filosof sjekke om gaffelen til høyre sidemann er ledig; hvis den ikke er det, legger filosofen fra seg sin egen gaffel og tenker litt til. #define N 5 #define LEFT(x) ((x)==0? N-1 : (x)-1) #define RIGHT(x) ((x)==n-1? 0 : (x)+1) philosopher(i) /* Hva skal filosof i gjøre? */ int i; { while (TRUE) { think(); take_fork(i); if (free_fork(right(i)) { /* Sjekk om ledig. */ eat(); put_fork(right(i)); put_fork(i); (Her vil kallet på free fork returnere med 0 (= FALSE) hvis gaffelen ikke er ledig. Hvis den er ledig, blir den tatt, så vi trenger ikke kalle på take fork.) Forelesning 5.5.1999 Ark 26 av 28
Vurdering + Denne løsningen vil stort sett gå bra, særlig hvis vi vet at tiden filosofene tenker, varierer tilfeldig. I meget spesielle tilfelle oppstår det utsulting. Filosofene vil ta opp og legge ned gafler i takt uten at noen får spise. Konklusjon: Brukbar, med mindre vi ønsker 100% sikkerhet. En løsning som virker Den kommer neste uke. Forelesning 5.5.1999 Ark 27 av 28
Oppsummering Teknikk Synk Data Spesielt UNIX filer UNIX rør Forutsetter samme urprosess Kan ikke alltid brukes. Delt lager Semaforer Forelesning 5.5.1999 Ark 28 av 28