Sodelovanje med nitmi
Niti posamezne skupine, ki je ustvarjena z ukazom #pragma omp parallel
, se izvajajo neodvisno, a večinoma sodelujejo pri reševanju istega problema. To pomeni, da morajo med seboj sodelovati in komunicirati. Komunikacija med nitmi poteka v glavnem preko skupnih spremenljivk, pri tem pa se skrivajo različne pasti. Kot primer si poglejmo naslednji program, ki sešteje oznake vseh niti:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Spremenljivka sum
je uporabljena kot skupna spremenljivka, ki služi za komunikacijo med nitmi: vsaka nit tej spremenljivki prišteje svojo oznako, a program kljub temu ne deluje pravilno. Še več, večina niti ob koncu ipiše povsem napačno vsoto oznak.
Vaja
Prepričajte se, da program error-1.c res deluje napačno.
Do napake pride zato, ker v 12. vrstici več niti hkrati izvede prištevanje oznake isti spremenljivki. Več niti torej hkrati prebere iz pomnilnika vrednost spremenljivke sum
, ji prišteje svojo oznako in zapiše rezultat nazaj v pomnilnik. Seveda se ohrani le zadnji zapis v pomnilnik, ostali so izgubljeni.
Atomarna operacija
En način reševanja problema, ko več niti hkrati izvaja operacijo nad skupno spremenljivko, je izvedba te operacije v enem nedeljivem koraku. Temu pravimo atomarna operacija. Da z orodjem OpenMP neko enostavno operacijo izpeljemo kod nedeljivo celoto, uporabimo ukaz #pragma omp atomic
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Zaradi ukaza #pragma omp atomic
vsaka nit programa synchro-atomic-1.c
- prebere spremenljivko
sum
iz pomnilnika, - ji prišteje vrednost spremenljivke
n
in - spremenljivko
sum
zapiše nazaj v pomnilnik
ne da bi katerakoli druga nit imela dostop do spremenljivke sum
. Zato program synchro-atomic-1.c pravilno izračuna vsoto oznak niti.
Vaja
Kljub temu, da program synchro-atomic-1.c pravilno izračuna vsoto oznak niti, večina niti še vedno izpiše napačno vsoto. Zakaj?
Atomarne operacije so lahko podane le kot stavki, ki so razmeroma enostavni izrazi in prirejanja. V ukazu #pragma omp atomic
lahko določimo, za kakšen atomaren dostop do spremenljivke gre:
- določilo
read
omogoči atomarno branje spremenljivke x v stavku expr=x; - določilo
write
omogoči atomarno pisanje spremenljivke x v stavku x=expr; - določilo
update
omogoči atomarni branje-in-pisanje spremenljivke x v stavku x=x+expr.
Kritična sekcija
Drug način reševanja problema, ko več niti hkrati izvaja operacijo nad skupno spremenljivko, je uporaba kritične sekcije. To je del niti, za katerega mora veljati, da ga v vsakem trenutku izvaja kvečjemu ena sama nit. Problem seštevanja oznak niti z uporabo kritične sekcije rešimo precej podobno kot z uporabo atomarne operacije. Tokrat uporabimo ukaz #pragma omp critical
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Čeprav je na prvi pogled rešitev precej podobna, omogoča kritična sekcija izvajanje ne le enostavnih izrazov in prirejanj, pač pa povsem poljubne kode, če je ta koda lepo zaprta v blok. Predvsem pa lahko kritični sekciji damo ime (v programu synchro-critical-1.c je to adding_thread_numbers
), kar omogoča, da se ista kritična sekcija pojavlja večkrat v programu (ime kritične sekcije je celo vidno zunaj prevajalne enote).
Vaja
Kljub temu, da program synchro-critical-1.c pravilno izračuna vsoto oznak niti, večina niti še vedno izpiše napačno vsoto. Zakaj?
Vaja
V program synchro-critical-1.c dodajte števec prištevanj spremenljivki sum
. Obe operaciji, prištevanje spremenljivki sum
in povečevanje števca prištevanj opravite v isti kritični sekciji, ob enem pa vsakič še izpišite vrednost števca.
Sinhronizacijska pregrada
V določenih primerih se morajo niti posamezne skupine na določenem mestu sinhronizirati: tiste niti, ki so svoje računanje to tega mesta opravile hitreje, morajo počakati vse ostale, da prirejo do tega mesta v programu. Na ta način pogosto rešujemo probleme z zagotavljanjem konsistentnosti podatkov v skupnem pomnilniku in vhodno-izhodnimi operacijami. Na mesto v programu, kjer naj se niti sinhronizirajo, postavimo sinhronizacijsko pregrado. To naredimo z ukazom #parallel omp barrier
.
V programu synchro-atomic-1.c mnoge niti takoj potem, ko prištejejo svojo oznako skupni vsoti, izpišejo vrednost spremenljivke sum
ne da bi počakale, da to isto storijo tudi ostale niti. Da tak predčasen izpis onemogočimo, pred izpis postavimo sinhronizacijsko pregrado: šele ko jo bodo dosegle vse niti in bodo torej vse oznake prištete skupni vsoti, bo nitim omogočen prehod preko sinhronizacijske pregrade in s tem izpis sprmenljivke sum
znotrej niti. To je prikazano s programom synchro-atomic-barrier-1.c.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Vaja
Dopolnite še program synchro-critical-1.c, da bo vsaka nit izpisala pravilno vsoto oznak niti.
Na tem mestu povejmo še to, da je na koncu vsakega nitenja, torej na koncu dela kode, ki je označen z ukazom #pragma omp parallel
, vedno postavljena sinhronizacijska pregrada.
Omejitev računanja na eno nit
Ne tem mestu omenimo še dva dodatna ukaza orodja OpenMP: #pragma omp master
in #pragma omp single
. Oba povzročita, da se koda, ki je označena z vsakim od teh dveh ukazov, izvrši le v eni sami niti, v ostalih nitih skupine pa ne:
- koda označena z ukazom
#pragma omp master
se izvrši le v glavni niti, ne pri vstopu ne pri izstopu iz označene kode pa ni nobene sinhronizacijske pregrade; - koda označena z ukazom
#pragma omp single
se izvrši le v eni naključno izbrani niti, pri izstopu pa je postavljena sinhronizacijska pregrada (ki jo je moč umakniti z dopolnilomnowait
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Vaja
Vsaka nit programa single-1.c pravilno izpiše vsoto oznak niti. Zakaj?
Niti programa master-1.c ne izpišejo vsote oznak niti pravilno. Zakaj?
Dopolnite program "master-1.c" tako, da bodo vse niti pravilno izpisale vsoto oznak niti.
Vaja
Spremenite programa master-1.c in single-1.c tako, da delujeta enako, a pri tem nista uporabljena ukaza #pragma omp master
in #pragma omp single
.