A FORTRAN-nak MIMD (Multiple Instruction stream Multiple Data stream, egymástól független processzorok által támogatott párhuzamosság) és SIMD (Single Instruction Multiple Data, vektorprocesszorokban használt párhuzamos technika, de ilyen az MMX, 3DNow! és SSE is) típusú párhuzamos programozást lehetővé tevő változata létezik.
MIMD
MIMD programokat írhatunk a PVM 3.1 programcsomag felhasználásával, Fortran 77 nyelven. A PVM egy párhuzamos programok írását támogató függvénykönyvtár. A PVM függvények a következő csoportokba oszthatók: folyamat vezérlés, információ lekérdezés, dinamikus konfigurálás, dinamikus folyamat csoportok kezelése, üzenet pufferek, szignálok, üzenetküldés, üzenet fogadás, hibakezelés.
Alább láthatók a PVM 3.1 Fortran felületének függvényei.
Process Control
pvmfmytid( tid )
pvmfexit( info )
pvmfkill( tid, info )
pvmfspawn( task,flag,where,ntask,tids,numt )
Information
pvmfparent( tid )
pvmfpstat( tid, pstat )
pvmfmstat( host, mstat )
pvmfconfig( nhost, narch, info )
pvmftasks( which, ntask, info )
Dynamic Configuration
pvmfaddhost( host, info )
pvmfdelhost( host, info )
Dynamic Process Groups
pvmfjoingroup( group, inum )
pvmflvgroup( group, info )
pvmfgsize( group, size, info )
pvmfgettid( group, inum, tid )
pvmfgetinst( group, tid, inum )
pvmfbarrier( group, count, info )
pvmfbcast( group, msgtag, info )
Message Buffers
pvmfmkbuf( encoding, bufid )
pvmffreebuf( bufid, info )
pvmfgetsbuf( bufid )
pvmfgetrbuf( bufid )
pvmfsetsbuf( bufid, oldbuf )
pvmfsetrbuf( bufid, oldbuf 0
pvmfinitsend( encoding, bufid )
Signalling
pvmfsendsig( tid, signum, info )
pvmfnotify( about, msgtag, ntask, tids, info )
Sending
pvmfpack( what, xp, nitem, stride, info )
pvmfsend( tid, msgtag, bufid )
pvmfmcast( ntask, tids, msgtag, info )
Receiving
pvmfrecv( tid, msgtag, bufid )
pvmfprobe( tid, msgtag, bufid )
pvmfnrecv( tid, msgtag, bufid )
pvmfbufinfo( bufid, bytes, msgtag, tid, info)
pvmfunpack( what, xp, nitem, stride, info )
Error Handling
pvmfperror( msg, info )
pvmfserror( set, oldset )
SIMD
SIMD programok jellemzője, hogy nagy adatmennyiségen kell ugyanazt a műveletsorozatot (lehetőleg párhuzamosan) elvégezni. A program szekvenciálisan és párhuzamosan végrehajtható részekre bontható. A FORTRAN 90 és a HPF lehetővé teszi adatpárhuzamos FORTRAN programok írását.
Alapvető újdonság a tömbműveletek és tömboperátorok megjelenése. Összeadhatunk, összeszorozhatunk azonos dimenziójú tömböket, illetve tömbök részeivel is végezhetünk aritmetikai műveleteket.
Tömb műveletek
A Fortran 90 nyelv alapvető nyelvi újdonsága - amely a párhuzamosság bevezetését lehetővé tette - a tömbökkel végzett műveletek egyszerűbb írásmódja.
Az F77-ben megszokott skalár műveleteket a Fortran 90-ben már tömbökre is lehet alkalmazni. Az így leírt műveleteket a fordító a kifejezésben szereplő tömbök megfelelő elemeire fogja alkalmazni. Az egyértelmű szemantika érdekében a műveletekben szereplő tömbök dimenzióinak és méreteinek meg kell egyezniük.
A következő példában a B tömb megfelelő elemeinek és c változónak az összege kerül bele A tömb megfelelő elemeibe. A beépített függvények - mint a gyökvonás - is használhatóak.
integer A(10,10), B(10,10), c
A = B + c
A = A + 1.0
A = SQRT(A)
A tömbműveleteknél a kifejezésekbe persze nem csak egész tömbök, hanem azok részei is behelyettesíthetőek. Résztömböt a megfelelő koordináta helyére írt számhármassal lehet megadni: alsó-határ:felső-határ:lépésköz. A tömb lépésköznyi távolságra lévő elemei az alsó határtól a felső határig lépkedve lesznek a résztömb elemei. Ilyen specifikációt hiányosan és több koordináta helyén is meg lehet adni.
integer A(4,8), B(4,8)
A(2, :) ! A masodik sora
A(2, 2:7) ! A masodik soranak kozepe
A(2, 1:8:2) ! A masodik sorabol minden
! masodik elem
! B tomb eltolasa, es hozzaadasa A-hoz
A(:, 1:7) = A(:, 1:7) + B(:, 2:8)
Tömb operátorok
A Fortran 90 nyelv egy argumentumú beépített műveletek - mint a gyökvonás - alkalmazását tömbökre is megengedi. Ebben az esetben az operátort a program a tömb minden elemére külön-külön végrehajtja. Ilyen pl.: SQRT, SIN, TAN, ABS ...
Vannak persze olyan műveletek is, amelyek egész tömböket is érintenek. Ezek közül a következőek vannak definiálva:
- SUM(A) A elemeinek az összege
- PRODUCT(A) A elemeinek szorzata
- MAXVAL(A) A legkisebb eleme
- MINVAL(A) A legnagyobb eleme
- MAXLOC(A) A legkisebb elemének az indexei
- MINLOC(A) A legnagyobb elemének az indexei
- MATMUL(A,B) A és B mátrixok szorzata
- DOTPRODUCT(A,B) A és B vektorok szorzata
- TRANSPOSE(A) A transzponáltja
- CSIHIFT(A, DIM, SHIFT) A mátrix eltoltja
Az előbb felsorolt függvények használata lehetővé teszi, hogy a fordító felismerje a párhuzamosítható részeket. A hatékonyság érdekében szükség lehet arra, hogy az adatok szétosztását a programozó befolyásolhassa. Mátrix deklarációkban rendelkezhetünk az adatok általunk javasolt szétosztásáról. (Különben a fordító dönt.)
Az összeg, szorzat, minimum és maximum műveletek az egész tömbre értendők, tekintet nélkül annak alakjára, dimenzióinak számára.
A maximum és minimum helyek keresésekor az eredményt az argumentum tömb dimenziószámának megfelelő nagyságú tömbbe fogja beletenni az operátor:
integer A(10,10), B(20, 10), P(2), s
...
P = MAXLOC(A)
s = SUM(A)
B(11:20, :) = MATMUL(A, B(1:10, :)
Egyes műveleteknél szükség lehet a szomszédos mátrix elemekre. Ilyen esetekben használható a CSHIFT művelet, mellyel a mátrixot valamelyik dimenzió irányában körkörösen el lehet forgatni:
result = CSHIFT(X, 2, -1) ! masodik dimenzioban egy
! elemmel visszaforgat
1 2 3 3 1 2
4 5 6 -> 6 4 5
7 8 9 9 7 8
CSHIFT(X, 2, -1)
result = CSHIFT(X, -1) ! elso dimenzional eleg
! egy parameter
1 2 3 7 8 9
4 5 6 -> 1 2 3
7 8 9 4 5 6
CSHIFT(X, -1)
A következő átlagolási feladat az eltolást használva sokkal egyszerűbben leírható:
real X(0:99), B(0:99)
do i = 0,99
B(i) = ( X(mod(i+99,100) + X(mod(i+1,100)) )/2
enddo
F90-ben:
real X(100), B(100), L(100), R(100)
L = CSHIFT(X,+1)
R = CSHIFT(X,-1)
B = ( L + R )/2
vagy még egyszerűbben:
real X(100), B(100)
B = ( CSHIFT(X,+1) + CSHIFT(X,-1) )/2
A HPF, illetve más párhuzamos Fortran kiterjesztések ezeken kívül még operátorok gazdag választékát nyújtják, például asszociatív műveletek részeredményeit is kiszámoló eszközöket.
Explicit párhuzamosságot támogató konstrukciók a WHERE, FORALL, INDEPENDENT és PURE. A WHERE segítségével egy tömb minden elemére végrehajtandó utasítás alternatívát programozhatunk. A FORALL a párhuzamosan végrehajtott ciklus kulcsszava, az INDEPENDENT hagyományos ciklus párhuzamosítására való. A PURE direktívával definiált függvények hívhatók több példányban elindítható programrészekből. Egy PURE függvényben nem lehet globális változóra hivatkozni, és a paraméterek értékét megváltoztatni.
OpenMP programozói környezet
Az OpenMP a fő informatikai cégek által definiált interfész, hordozható, skálázható eszközt nyújt a fejlesztők számára elosztott-memória modellel. Az API C/C++ és Fortran nyelveket támogatja számos architektúrán: többek között UNIX és Windows.
Mi is az OpenMP?
- Egy API amely többszálú, osztott-memória modellre épül
- Három alapvető komponenst tartalmaz:
- Fordítónak szóló direktívák
- Futtatási környezethez könyvtárak
- Környezeti változók
- Hordozható
- ANSI szabvány-tervezett
Linkek:
Jelenlegi változat: OpenMP 3.0
OpenMP programozói modell
- Fork -Join Modell
- Minden program a fő- vagy mesterszálon indul el. Ez szekvenciális porgramvégrehajtást jelent az első párhuzamos régióig (parallel region).
- FORK: A főszál párhuzamosan futó szálakat hoz létre egy tímben.
- JOIN:Ha a tím összes szála befejezte tevékenységét a befejező szinkronizációs pontnál, akkor csak a főszál fut tovább.
- Beágyazott újabb párhuzamos szálak indítása-implementáció függő
- Dinamikus szálkezelés-implementáció függő
Példa egy OpenMP kódszerkezetre
PROGRAM HELLO
INTEGER VAR1, VAR2, VAR3
Szekvencialis kod
.
.
.
A parhuzamos feldolgozas kezdete-több szál indítása.
A változok ervenyessege
!$OMP PARALLEL PRIVATE(VAR1, VAR2) SHARED(VAR3)
Parhuzamosan vegrehajtando minden szal eseten
.
.
.
Miden szal kilepo szinkronizacios pontja.
!$OMP END PARALLEL
Szekvencialis vegrehajtas.
.
.
.
END
Párhuzamos régió létrehozása
Célja egy blokk kijelölése párhuzamos végrehajtásra
Formátum:
!$OMP PARALLEL [clause ...]
IF (scalar_logical_expression)
PRIVATE (list)
SHARED (list)
DEFAULT (PRIVATE | FIRSTPRIVATE | SHARED | NONE)
FIRSTPRIVATE (list)
REDUCTION (operator: list)
COPYIN (list)
NUM_THREADS (scalar-integer-expression)
block
!$OMP END PARALLEL
A "Hello World" példaprogram
PROGRAM HELLO
INTEGER NTHREADS, TID, OMP_GET_NUM_THREADS,
+ OMP_GET_THREAD_NUM
C Szalak letrehozasa TID azonosoitoval
!$OMP PARALLEL PRIVATE(TID)
C Obtain and print thread id
TID = OMP_GET_THREAD_NUM()
PRINT *, 'Hello World from thread = ', TID
C Only master thread does this
IF (TID .EQ. 0) THEN
NTHREADS = OMP_GET_NUM_THREADS()
PRINT *, 'Number of threads = ', NTHREADS
END IF
C Minden szal itt talalkozik es megszunik
!$OMP END PARALLEL
END
1. Feladat-kiosztás DO direktívával
Egyszerű vektor összeadó program
- Az A,B,C tömbök és az N változó közös tárterületen
- Az I változó privát minden egyes futó szál esetén
- Az iterációk dinamikusan vannak kiosztva, CHUNK méretű darabokban
- Az egyes szálak nem várakoznak szinkornizációs pontban
PROGRAM VEC_ADD_DO
INTEGER N, CHUNKSIZE, CHUNK, I
PARAMETER (N=1000)
PARAMETER (CHUNKSIZE=100)
REAL A(N), B(N), C(N)
! Some initializations
DO I = 1, N
A(I) = I * 1.0
B(I) = A(I)
ENDDO
CHUNK = CHUNKSIZE
!$OMP PARALLEL SHARED(A,B,C,CHUNK) PRIVATE(I)
!$OMP DO SCHEDULE(DYNAMIC,CHUNK)
DO I = 1, N
C(I) = A(I) + B(I)
ENDDO
!$OMP END DO NOWAIT
!$OMP END PARALLEL
END
2. Feladat-kiosztás SECTIONS direktívával
Minden egyes így kijelölt blokkot egy szál hajt végre-különböző blokkok külön szálakhoz.
PROGRAM VEC_ADD_SECTIONS
INTEGER N, I
PARAMETER (N=1000)
REAL A(N), B(N), C(N), D(N)
! Some initializations
DO I = 1, N
A(I) = I * 1.5
B(I) = I + 22.35
ENDDO
!$OMP PARALLEL SHARED(A,B,C,D), PRIVATE(I)
!$OMP SECTIONS
!$OMP SECTION
DO I = 1, N
C(I) = A(I) + B(I)
ENDDO
!$OMP SECTION
DO I = 1, N
D(I) = A(I) * B(I)
ENDDO
!$OMP END SECTIONS NOWAIT
!$OMP END PARALLEL
END
3. Feladat kiosztás WORKSHARE direktívával
A kijelölt blokk csak a következőt tartalmazhatja:
- tömb értékadás
- skalár értékadás
- FORALL konstrukció
- WHERE konstrukció
- atomi konstrukció
- kritikus konstrukció
PROGRAM WORKSHARE
INTEGER N, I, J
PARAMETER (N=100)
REAL AA(N,N), BB(N,N), CC(N,N), DD(N,N), FIRST, LAST
! Some initializations
DO I = 1, N
DO J = 1, N
AA(J,I) = I * 1.0
BB(J,I) = J + 1.0
ENDDO
ENDDO
!$OMP PARALLEL SHARED(AA,BB,CC,DD,FIRST,LAST)
!$OMP WORKSHARE
CC = AA * BB
DD = AA + BB
FIRST = CC(1,1) + DD(1,1)
LAST = CC(N,N) + DD(N,N)
!$OMP END WORKSHARE NOWAIT
!$OMP END PARALLEL
END
Szinkronizáció CRITICAL direktívával
Ha egy futó szál a kritikus blokkban van, akkor egy másik szál várakozik a belépési pontnál mindaddig, amíg az előző szál el nem hagyja a kijelölt szakaszt.
PROGRAM CRITICAL
INTEGER X
X = 0
!$OMP PARALLEL SHARED(X)
!$OMP CRITICAL
X = X + 1
!$OMP END CRITICAL
!$OMP END PARALLEL
END
A THREADPRIVATE direktíva
A direktíva a változó deklaráció után következik. Minden egyes szál saját másolatot kap ezen változókról, mely nem elérhető máshonnan.
PROGRAM THREADPRIV
INTEGER A, B, I, TID, OMP_GET_THREAD_NUM
REAL*4 X
COMMON /C1/ A
!$OMP THREADPRIVATE(/C1/, X)
C Explicitly turn off dynamic threads
CALL OMP_SET_DYNAMIC(.FALSE.)
PRINT *, '1st Parallel Region:'
!$OMP PARALLEL PRIVATE(B, TID)
TID = OMP_GET_THREAD_NUM()
A = TID
B = TID
X = 1.1 * TID + 1.0
PRINT *, 'Thread',TID,': A,B,X=',A,B,X
!$OMP END PARALLEL
PRINT *, '************************************'
PRINT *, 'Master thread doing serial work here'
PRINT *, '************************************'
PRINT *, '2nd Parallel Region: '
!$OMP PARALLEL PRIVATE(TID)
TID = OMP_GET_THREAD_NUM()
PRINT *, 'Thread',TID,': A,B,X=',A,B,X
!$OMP END PARALLEL
END
Output:
1st Parallel Region:
Thread 0 : A,B,X= 0 0 1.000000000
Thread 1 : A,B,X= 1 1 2.099999905
Thread 3 : A,B,X= 3 3 4.300000191
Thread 2 : A,B,X= 2 2 3.200000048
************************************
Master thread doing serial work here
************************************
2nd Parallel Region:
Thread 0 : A,B,X= 0 0 1.000000000
Thread 2 : A,B,X= 2 0 3.200000048
Thread 3 : A,B,X= 3 0 4.300000191
Thread 1 : A,B,X= 1 0 2.099999905
REDUCTION klauzula
A listában felsorolt változóról egy másolat készül minden szál esetén. A kijelölt blokk után az eredmény a globális osztott memóriahelyre kerül.
PROGRAM DOT_PRODUCT
INTEGER N, CHUNKSIZE, CHUNK, I
PARAMETER (N=100)
PARAMETER (CHUNKSIZE=10)
REAL A(N), B(N), RESULT
! Some initializations
DO I = 1, N
A(I) = I * 1.0
B(I) = I * 2.0
ENDDO
RESULT= 0.0
CHUNK = CHUNKSIZE
!$OMP PARALLEL DO
!$OMP& DEFAULT(SHARED) PRIVATE(I)
!$OMP& SCHEDULE(STATIC,CHUNK)
!$OMP& REDUCTION(+:RESULT)
DO I = 1, N
RESULT = RESULT + (A(I) * B(I))
ENDDO
!$OMP END PARALLEL DO NOWAIT
PRINT *, 'Final Result= ', RESULT
END
Megszorítások
- A listabeli változó csak skaláris lehet, tömb vagy struktúra nem. SHARED környezetben szükséges deklarálni.
-
- Csak a következő formák megengedettek: x=x op expr vagy x= expr op x , x=intrinsic(x,expr), x=intrinsic(expr,x), ahol op:+,*,-,.AND.,.OR.,.EQV.,.NEQV. intrinsic:MAX, MIN, IAND, IOR, IEOR
NOWAIT direktíva
Bizonyos körülmények között nem szükséges minden futó szálnak befejeződnie a program adott pontján- a következő művelet nem függ az előzőektől.
Az ilyen esetekben kihagyható az implicit szinkronizáció:
!$OMP PARALLEL
!$OMP DO
do i = 1, 1000
a = i
enddo
!$OMP END DO NOWAIT
!$OMP DO
do i = 1, 1000
b = i
enddo
!$OMP END DO
!$OMP END PARALLEL
SCHEDULE(type, chunk) direktíva
Ha egy do-loop ciklus minden egyes iterációját szeretnénk több szál között
kiosztani, akkor legegyszerűbb azonos számú iterációt hozzárendelni a szálakhoz.
Ezt valósítja meg a STATIC kiosztás:
!$OMP DO SCHEDULE(STATIC, chunk)
do i = 1, 600
...
enddo
!$OMP END DO
Az opcionális chunk paraméter a kiosztott iterációk mennyiségét határozza
meg, de alapértelmezésben azonos az egyes szálak között.
Ha az egyes iterációk nem homogének, akkor a dinamikus hozzárendelést
érdemes választani:
!$OMP DO SCHEDULE(DYNAMIC,256)
do i = 1, 10230
...
enddo
!$OMP END DO
Az opcionális chunk paraméter az iterációk számát adja meg, alapértelmezésben
egyet szálanként.
Ha az adott szál befejezte a ciklus törzsét, akkor egy
újabb iterációs blokkot kap.
Az előző módszer a statikus kiosztáshoz képest nagyobb teljesítményre képes,
viszont járulékos számítást igényel a különböző iterációk kiosztásánál.
A másik megközelítés egyre kisebb egységeket oszt ki az egyes szálak között:
!$OMP DO SCHEDULE(GUIDED,256)
do i = 1, 10230
...
enddo
!$OMP END DO
A kiosztott iterációk számát felezi az OpenMP futattási környezet, ahol a chunk paraméter a legkisebb egységet határozza meg.
A fent említett módszerek még fordítási időben határozzák meg a szálak kiosztását, de lehetőség van ezt futási időben is megtenni:
$OMP DO SCHEDULE(RUNTIME)
Ebben az esetben az OMP_SCHEDULE környezeti változó értéke határozza meg a kiosztást.
ORDERED direktíva
Ha az iterációt a megadott sorrendben kell végrehajtani, akkor ezt előírhatjuk az !$OMP ORDERED/!$OMP END ORDERED direktívapárral:
!$OMP DO ORDERED
do i = 1, 1000
!$OMP ORDERED
A(i) = 2 * A(i-1)
!$OMP END ORDERED
enddo
!$OMP END DO
Az OpenMP futtatási környezet könyvtári rutinjai
OMP_set_num_threads szubrutin
Ez a szubrutin a végrahajtási szálak számát határozza meg a következő párhuzamos blokkban:
subroutine OMP_set_num_threads(number_of_threads)
integer(kind = OMP_integer_kind), intent(in) :: number_of_threads
end subroutine OMP_set_num_threads
Ha a szálak dinamikus kiosztása megengedett, akkor ez a maximális , egyébként pedig a futtatható szálakat adja meg.
OMP_get_num_threads szubrutin
Ez a függvény a jelenleg futó szálak számát adja vissza, illetve egyet szekvenciális blokk esetén:
function OMP_get_num_threads()
integer(kind = OMP_integer_kind) :: OMP_get_num_threads
end function OMP_get_num_threads
OMP_get_max_threads szubrutin
Ez a függvény a maximális elérhető szálak számát adja vissza, ami különbözhet az előző függvény értékétől dinamikus kiosztás esetén:
function OMP_get_max_threads()
integer(kind = OMP_integer_kind) :: OMP_get_max_threads
end function OMP_get_max_threads
OMP_get_thread_num szubrutin
A szál azonosítóját adja vissza.A szálak 0-tól (mester) vannak számozva.
function OMP_get_thread_num()
integer(kind = OMP_integer_kind) :: OMP_get_thread_num
end function OMP_get_thread_num
OMP_getnum_procs szubrutin
Az elérhető processzorok számát adja vissza:
function OMP_get_num_procs()
integer(kind = OMP_integer_kind) :: OMP_get_num_procs
end function OMP_get_num_procs
OMP_set_dynamic szubrutin
A szubrutin tiltja vagy engedélyezi a dinamikus szálkiosztást:
subroutine OMP_set_dynamic(enable)
logical(kind = OMP_logical_kind), intent(in) :: enable
end subroutine OMP_set_dynamic
Ha a megadott érték .TRUE., akkor a futtató környezet dinamikusan kezeli a szálakat az adott SMP architektúrán. Az alapbeállítás implementációként változik.
OMP_get_dynamic szubrutin
Ez a függvény visszaadja a dinamikus szálkiosztás beállítását:
function OMP_get_dynamic()
logical(kind = OMP_logical_kind) :: OMP_get_dynamic
end function OMP_get_dynamic
OMP_set_nested szubrutin
Ez a függvény megengedi/tiltja a beágyazott többszálú feldolgozást:
subroutine OMP_set_nested(enable)
logical(kind = OMP_logical_kind), intent(in) :: enable
end subroutine OMP_set_nested
Alapbeállításként .FALSE., vagyis a futtató környezet sorosan dolgozza fel a belső blokkokat. A szálak száma implementáció-függő.
OMP_get_nested szubrutin
Ez a függvény az OMP_set_nested függvény lekérdező párja:
function OMP_get_nested()
logical(kind = OMP_logical_kind) :: OMP_get_nested
end function OMP_get_nested
Zároló függvények
A zárolás jelzőként értelmezheő, amely ki/be kapcsolható. Minden szál ellenőrheti a zárolás állapotát. Az állapotot beállító szál a zárolás tulajdonosa.
Ez addig érvényes, amíg ki nem kapcsolja a zárat az adott szál. A mechanizmus abban különbözik az előzőekben felvázolt, hogy a lekérdező szálaknak nem kell figyelmbe venniük a zárolást, amely flexibilis kódot eredményez.
program Main
use omp_lib
implicit none
integer(kind = OMP_lock_kind) :: lck
integer(kind = OMP_integer_kind) :: ID
call OMP_init_lock(lck)
!$OMP PARALLEL SHARED(LCK) PRIVATE(ID)
ID = OMP_get_thread_num()
do while(.not.OMP_test_lock(lck))
... !dolgozz addig, amig várakozni kell
enddo
... !feladat amit a zarolas alatt kell elvegezni
!$OMP END PARALLEL
call OMP_destroy_lock(lck)
end program Main
Ebben a példában minden szál, amely nem birtokolja a zárat, más feladatot tud végrehajtani.
A következő kódrészlet a beágyazott zárolásra ad példát:
module data_types
use omp_lib, only: OMP_nest_lock_kind
implicit none
type number
integer :: n
integer(OMP_nest_lock_kind) :: lck
end type number
end module data_types
!---------------------------------------------------------!
program Main
use omp_lib
use data_types
implicit none
type(number) :: x
x%n = 0
call OMP_init_lock(x%lck)
!$OMP PARALLEL SECTIONS SHARED(x)
!$OMP SECTION
call add(x,20)
!$OMP SECTION
call substract(x,10)
!$OMP END PARALLEL
call OMP_destroy_lock(lck)
end program Main
!---------------------------------------------------------!
subroutine add(x,d)
use omp_lib
use data_types
implicit none
type(number) :: x
integer :: d
call OMP_set_nest_lock(x%lck)
x%n = x%n + d
call OMP_unset_nest_lock(x%lck)
end subroutine add