Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
Az alábbiakban példákat láthat a dokumentumban definiált szerkezetekre. Az irányelvet követő nyilatkozat csak akkor összetett, ha szükséges, és az azt megelőző irányelvnél egy nem összetett nyilatkozatot behúznak.
A.1 Egy egyszerű hurok párhuzamosan
Az alábbi példa bemutatja, hogyan lehet párhuzamosítani egy hurkot a parallel for direktíva használatával. A hurok iterációs változója alapértelmezés szerint privát, ezért nem szükséges explicit módon megadni egy privát záradékban.
#pragma omp parallel for
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
A.2 Feltételes kompilálás
Az alábbi példák az OpenMP-makró _OPENMP használatával történő feltételes fordítás használatát szemléltetik. Az OpenMP fordítással a _OPENMP makró lesz definiálva.
# ifdef _OPENMP
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
A definiált előfeldolgozó operátor lehetővé teszi, hogy egyetlen irányelvben több makrót is teszteljenek.
# if defined(_OPENMP) && defined(VERBOSE)
printf_s("Compiled by an OpenMP-compliant implementation.\n");
# endif
A.3 Párhuzamos régiók
A párhuzamos irányelv durva szemcsés párhuzamos programokban is használható. A következő példában a párhuzamos régió minden szála a szálszám alapján dönti el, hogy a globális tömb x melyik részével dolgozzon.
#pragma omp parallel shared(x, npoints) private(iam, np, ipoints)
{
iam = omp_get_thread_num();
np = omp_get_num_threads();
ipoints = npoints / np;
subdomain(x, iam, ipoints);
}
A.4 Az "nowait" záradék
Ha számos független hurok található egy párhuzamos régióban, a nowait záradék használatával elkerülheti a beépített akadályt az irányelv végén, az alábbi módon:
#pragma omp parallel
{
#pragma omp for nowait
for (i=1; i<n; i++)
b[i] = (a[i] + a[i-1]) / 2.0;
#pragma omp for nowait
for (i=0; i<m; i++)
y[i] = sqrt(z[i]);
}
A.5 A kritikus irányelv
Az alábbi példa számos kritikus irányelvet tartalmaz. A példa egy sorban állási modellt mutat be, amelyben egy feladat kikerül a sorból és feldolgozásra kerül. Ha sok szál ellen szeretné védeni ugyanazt a feladatot, a lekérdezési műveletnek egy critical szakaszban kell lennie. Mivel a példában szereplő két üzenetsor független, különböző nevű irányelvek, critical és yaxisok védik őket.
#pragma omp parallel shared(x, y) private(x_next, y_next)
{
#pragma omp critical ( xaxis )
x_next = dequeue(x);
work(x_next);
#pragma omp critical ( yaxis )
y_next = dequeue(y);
work(y_next);
}
A.6 Az utolsó private záradék
A helyes végrehajtás néha attól az értéktől függ, amelyet a ciklus utolsó iterációja hozzárendel egy változóhoz. Az ilyen programoknak az összes ilyen változót argumentumként kell felsorolni egy utolsóprivate záradékban, hogy a változók értékei megegyeznek a ciklus egymás utáni végrehajtásakor.
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i=0; i<n-1; i++)
a[i] = b[i] + b[i+1];
}
a[i]=b[i];
Az előző példában a párhuzamos régió végének értéke i egyenlő n-1lesz, mint a szekvenciális esetben.
A.7 A csökkentési záradék
Az alábbi példa a csökkentési záradékot mutatja be:
#pragma omp parallel for private(i) shared(x, y, n) \
reduction(+: a, b)
for (i=0; i<n; i++) {
a = a + x[i];
b = b + y[i];
}
A.8 Párhuzamos szakaszok
Az alábbi példában (a 2.4.2. szakaszban) a xaxis, a yaxis és a zaxis függvény egyidejűleg végrehajtható. Az első section irányelv nem kötelező. Minden section direktívának a parallel sections szerkezet lexikális terjedelmében kell megjelennie.
#pragma omp parallel sections
{
#pragma omp section
xaxis();
#pragma omp section
yaxis();
#pragma omp section
zaxis();
}
A.9 Önálló irányelvek
Az alábbi példa az egységes irányelvet mutatja be. A példában csak egy szál (általában az a szál, amelyik először találkozik a single irányelvvel) írja ki a folyamatjelző üzenetet. A felhasználónak nem szabad feltételeznie, hogy melyik szál hajtja végre a szakaszt single . Minden más szál kihagyja a single szakaszt, és megáll a single konstrukció végén lévő akadálynál. Ha más szálak anélkül folytathatják a műveletet, hogy megvárnák a single szakaszt végrehajtó szálat, az irányelvben nowait meg lehet adni egy single záradékot.
#pragma omp parallel
{
#pragma omp single
printf_s("Beginning work1.\n");
work1();
#pragma omp single
printf_s("Finishing work1.\n");
#pragma omp single nowait
printf_s("Finished work1 and beginning work2.\n");
work2();
}
A.10 Szekvenciális rendezés
A rendezett szakaszok hasznosak a párhuzamosan végzett munka kimenetének sorrendbe helyezéséhez. Az alábbi program szekvenciális sorrendben nyomtatja ki az indexeket:
#pragma omp for ordered schedule(dynamic)
for (i=lb; i<ub; i+=st)
work(i);
void work(int k)
{
#pragma omp ordered
printf_s(" %d", k);
}
A.11 Rögzített számú szál
Egyes programok rögzített, előre meghatározott számú szálra támaszkodnak a helyes végrehajtáshoz. Mivel a szálak számának dinamikus módosítására vonatkozó alapértelmezett beállítás implementálási beállítás, az ilyen programok dönthetnek úgy, hogy kikapcsolják a dinamikus szálak képességeit, és explicit módon beállítják a szálak számát a hordozhatóság megőrzése érdekében. Az alábbi példa bemutatja, hogyan teheti ezt meg omp_set_dynamic és omp_set_num_threads használatával:
omp_set_dynamic(0);
omp_set_num_threads(16);
#pragma omp parallel shared(x, npoints) private(iam, ipoints)
{
if (omp_get_num_threads() != 16)
abort();
iam = omp_get_thread_num();
ipoints = npoints/16;
do_by_16(x, iam, ipoints);
}
Ebben a példában a program csak akkor működik helyesen, ha 16 szál hajtja végre. Ha az implementáció nem képes 16 szál támogatására, a példa viselkedése implementálási definícióval van meghatározva.
A párhuzamos régiót végrehajtó szálak száma állandó marad a párhuzamos régióban, függetlenül a dinamikus szálak beállításától. A dinamikus szálak mechanizmusa meghatározza a párhuzamos régió elején használandó szálak számát, és a régió időtartamára állandó marad.
A.12 Az atomi direktíva
Az alábbi példa az atomi direktíva használatával elkerüli a versenyfeltételeket (az x elem egyidejű frissítése több szálon):
#pragma omp parallel for shared(x, y, index, n)
for (i=0; i<n; i++)
{
#pragma omp atomic
x[index[i]] += work1(i);
y[i] += work2(i);
}
Az irányelv ebben a atomic példában való használatának előnye, hogy lehetővé teszi az x két különböző elemének párhuzamos frissítését. Ha ehelyett kritikus direktívát használ, akkor az x elemeinek összes frissítése sorosan lesz végrehajtva (bár nem garantált sorrendben).
Az atomic irányelv csak az azt követő C vagy C++ utasításra vonatkozik. Ennek eredményeképpen az y elemei nem frissülnek atomilag ebben a példában.
A.13 Egy listával rendelkező irányelv flush utasítás
Az alábbi példa az flush utasítást használja adott objektumok pont–pont közötti szinkronizálására a szálpárok esetében.
int sync[NUMBER_OF_THREADS];
float work[NUMBER_OF_THREADS];
#pragma omp parallel private(iam,neighbor) shared(work,sync)
{
iam = omp_get_thread_num();
sync[iam] = 0;
#pragma omp barrier
// Do computation into my portion of work array
work[iam] = ...;
// Announce that I am done with my work
// The first flush ensures that my work is
// made visible before sync.
// The second flush ensures that sync is made visible.
#pragma omp flush(work)
sync[iam] = 1;
#pragma omp flush(sync)
// Wait for neighbor
neighbor = (iam>0 ? iam : omp_get_num_threads()) - 1;
while (sync[neighbor]==0)
{
#pragma omp flush(sync)
}
// Read neighbor's values of work array
... = work[neighbor];
}
A.14 Lista nélküli törlési irányelv
Az alábbi példa ( a 2.6.5. szakaszban) megkülönbözteti az irányelv által flush érintett megosztott objektumokat, és nem tartalmaz listát a nem érintett megosztott objektumoktól:
// omp_flush_without_list.c
#include <omp.h>
int x, *p = &x;
void f1(int *q)
{
*q = 1;
#pragma omp flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
void f2(int *q)
{
#pragma omp barrier
*q = 2;
#pragma omp barrier
// a barrier implies a flush
// x, p, and *q are flushed
// because they are shared and accessible
// q is not flushed because it is not shared.
}
int g(int n)
{
int i = 1, j, sum = 0;
*p = 1;
#pragma omp parallel reduction(+: sum) num_threads(10)
{
f1(&j);
// i, n and sum were not flushed
// because they were not accessible in f1
// j was flushed because it was accessible
sum += j;
f2(&j);
// i, n, and sum were not flushed
// because they were not accessible in f2
// j was flushed because it was accessible
sum += i + j + *p + n;
}
return sum;
}
int main()
{
}
A.15 A felhasznált szálak száma
Vegye figyelembe a következő helytelen példát ( a 3.1.2. szakasz esetében):
np = omp_get_num_threads(); // misplaced
#pragma omp parallel for schedule(static)
for (i=0; i<np; i++)
work(i);
A omp_get_num_threads() hívás a kód soros szakaszában 1-et ad vissza, így az np mindig 1 lesz az előző példában. A párhuzamos régióban üzembe helyezendő szálak számának meghatározásához a hívásnak a párhuzamos régión belül kell lennie.
Az alábbi példa bemutatja, hogyan írhatja át ezt a programot anélkül, hogy lekérdezést adna hozzá a szálak számához:
#pragma omp parallel private(i)
{
i = omp_get_thread_num();
work(i);
}
A.16 zárak
A következő példában ( a 3.2. szakaszban) a zárolási függvények argumentumának típussal omp_lock_tkell rendelkeznie, és nem szükséges kiüríteni. A zárolási függvények miatt a szálak tétlenek lesznek, miközben az első kritikus szakaszba való belépésre várnak, de más műveleteket is végeznek, miközben a másodikra várnak. A omp_set_lock függvény blokkol, de a omp_test_lock függvény nem, így lehetővé téve a munkát skip().
// omp_using_locks.c
// compile with: /openmp /c
#include <stdio.h>
#include <omp.h>
void work(int);
void skip(int);
int main() {
omp_lock_t lck;
int id;
omp_init_lock(&lck);
#pragma omp parallel shared(lck) private(id)
{
id = omp_get_thread_num();
omp_set_lock(&lck);
printf_s("My thread id is %d.\n", id);
// only one thread at a time can execute this printf
omp_unset_lock(&lck);
while (! omp_test_lock(&lck)) {
skip(id); // we do not yet have the lock,
// so we must do something else
}
work(id); // we now have the lock
// and can do the work
omp_unset_lock(&lck);
}
omp_destroy_lock(&lck);
}
A.17 Beágyazott zárolások
Az alábbi példa ( a 3.2. szakaszhoz) bemutatja, hogyan lehet egy beágyazott zárolással szinkronizálni a frissítéseket egy teljes struktúrára és annak egyik tagjára.
#include <omp.h>
typedef struct {int a,b; omp_nest_lock_t lck;} pair;
void incr_a(pair *p, int a)
{
// Called only from incr_pair, no need to lock.
p->a += a;
}
void incr_b(pair *p, int b)
{
// Called both from incr_pair and elsewhere,
// so need a nestable lock.
omp_set_nest_lock(&p->lck);
p->b += b;
omp_unset_nest_lock(&p->lck);
}
void incr_pair(pair *p, int a, int b)
{
omp_set_nest_lock(&p->lck);
incr_a(p, a);
incr_b(p, b);
omp_unset_nest_lock(&p->lck);
}
void f(pair *p)
{
extern int work1(), work2(), work3();
#pragma omp parallel sections
{
#pragma omp section
incr_pair(p, work1(), work2());
#pragma omp section
incr_b(p, work3());
}
}
A.18 Beágyazott irányelvekhez
Az alábbi forirányelvbe ágyazás példája azért megfelelő, mert a belső és a külső for irányelvek különböző párhuzamos régiókhoz kötődnek.
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
{
#pragma omp parallel shared(i, n)
{
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
Az előző példa következő változata is megfelelő:
#pragma omp parallel default(shared)
{
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
void work1(int i, int n)
{
int j;
#pragma omp parallel default(shared)
{
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
return;
}
A.19 Példák a munkamegosztási irányelvek helytelen beágyazására
Az ebben a szakaszban szereplő példák az irányelv beágyazási szabályait szemléltetik .
Az alábbi példa nem megfelelő, mert a belső és külső for irányelvek beágyazottak és ugyanahhoz parallel az irányelvhez kötődnek:
void wrong1(int n)
{
#pragma omp parallel default(shared)
{
int i, j;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp for
for (j=0; j<n; j++)
work(i, j);
}
}
}
Az előző példa következő dinamikusan beágyazott verziója szintén nem megfelelő:
void wrong2(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++)
work1(i, n);
}
}
void work1(int i, int n)
{
int j;
#pragma omp for
for (j=0; j<n; j++)
work2(i, j);
}
Az alábbi példa nem megfelelő, mert a for és single irányelvek beágyazottak, és ugyanahhoz a párhuzamos régióhoz kapcsolódnak.
void wrong3(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
#pragma omp single
work(i);
}
}
}
Az alábbi példa nem megfelelően megfelel, mert egy barrier irányelv egy for belsejében holtpontot okozhat.
void wrong4(int n)
{
#pragma omp parallel default(shared)
{
int i;
#pragma omp for
for (i=0; i<n; i++) {
work1(i);
#pragma omp barrier
work2(i);
}
}
}
Az alábbi példa nem felel meg az előírásoknak, mert a barrier holtpontot okoz, mivel egyszerre csak egy szál léphet be a kritikus szakaszba.
void wrong5()
{
#pragma omp parallel
{
#pragma omp critical
{
work1();
#pragma omp barrier
work2();
}
}
}
Az alábbi példa nem megfelelő, mert a barrier holtpontot eredményez, mivel a single szakaszt csak egy szál hajtja végre.
void wrong6()
{
#pragma omp parallel
{
setup();
#pragma omp single
{
work1();
#pragma omp barrier
work2();
}
finish();
}
}
A.20 Kötéskorlátra vonatkozó irányelvek
Az irányelvkötési szabályok előírják, hogy a barrier irányelv a legközelebbi belefoglaló parallel irányelvhez kössék. Az irányelvkötésről további információt a 2.8. szakaszban talál.
Az alábbi példában a main-ból a sub2-be irányuló hívás megfelelő, mert a barrier (sub3) a sub2 párhuzamos régiójához kapcsolódik. A fősub1 alprogramba irányuló hívás azért megfelel, mert a barrier a sub2 alprogram párhuzamos régiójához kapcsolódik. A főrőlsub3-ra irányuló hívás megfelelő, mert a barrier nem kapcsolódik párhuzamos régióhoz, és figyelmen kívül van hagyva. Emellett a barrier csak a körülzáró párhuzamos régióban lévő szálak csoportját szinkronizálja, nem pedig az sub1-ben létrehozott összes szálat.
int main()
{
sub1(2);
sub2(2);
sub3(2);
}
void sub1(int n)
{
int i;
#pragma omp parallel private(i) shared(n)
{
#pragma omp for
for (i=0; i<n; i++)
sub2(i);
}
}
void sub2(int k)
{
#pragma omp parallel shared(k)
sub3(k);
}
void sub3(int n)
{
work(n);
#pragma omp barrier
work(n);
}
A.21 Hatókörváltozók a privát záradékkal
i A párhuzamos régióból való kilépéskor az alábbi példában és j értékei nem határozhatók meg:
int i, j;
i = 1;
j = 2;
#pragma omp parallel private(i) firstprivate(j)
{
i = 3;
j = j + 2;
}
printf_s("%d %d\n", i, j);
A záradékkal kapcsolatos további információkért lásd a private2.7.2.1. szakaszt.
A.22 Az alapértelmezett (nincs megadva) záradék
Az alábbi példa megkülönbözteti a záradék által default(none) érintett változókat azoktól a változóktól, amelyek nem:
// openmp_using_clausedefault.c
// compile with: /openmp
#include <stdio.h>
#include <omp.h>
int x, y, z[1000];
#pragma omp threadprivate(x)
void fun(int a) {
const int c = 1;
int i = 0;
#pragma omp parallel default(none) private(a) shared(z)
{
int j = omp_get_num_thread();
//O.K. - j is declared within parallel region
a = z[j]; // O.K. - a is listed in private clause
// - z is listed in shared clause
x = c; // O.K. - x is threadprivate
// - c has const-qualified type
z[i] = y; // C3052 error - cannot reference i or y here
#pragma omp for firstprivate(y)
for (i=0; i<10 ; i++) {
z[i] = y; // O.K. - i is the loop control variable
// - y is listed in firstprivate clause
}
z[i] = y; // Error - cannot reference i or y here
}
}
A záradékkal kapcsolatos további információkért lásd a default2.7.2.5. szakaszt.
A.23 Példák a megrendelt irányelvre
Lehetséges sok rendezett szakasz létrehozni, ha rendelkezésre áll egy for záradék, amit ordered-szel adnak meg. Az első példa nem megfelelő, mert az API a következő szabályt adja meg:
"Egy szerkezettel rendelkező for hurok iterációja nem hajthatja végre többször ugyanazt ordered az irányelvet, és nem hajthat végre egynél ordered több irányelvet." (Lásd a 2.6.6. szakaszt.)
Ebben a nem megfelelő példában minden iteráció két rendezett szakaszt hajt végre:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
#pragma omp ordered
{ ... }
...
#pragma omp ordered
{ ... }
...
}
Az alábbi megfelelő példa egynél for több rendezett szakaszt mutat be:
#pragma omp for ordered
for (i=0; i<n; i++)
{
...
if (i <= 10)
{
...
#pragma omp ordered
{ ... }
}
...
(i > 10)
{
...
#pragma omp ordered
{ ... }
}
...
}
A.24 Példa a privát záradékra
A párhuzamos régió privát záradéka csak a régió lexikális kiterjedésére vonatkozik, a régió dinamikus kiterjedésére nem. Ezért az alábbi példában a a változó bármilyen használata a for rutin ciklusán belül az a magánpéldányára utal, míg a g rutinban való használat a globális a-ra hivatkozik.
int a;
void f(int n)
{
a = 0;
#pragma omp parallel for private(a)
for (int i=1; i<n; i++)
{
a = i;
g(i, n);
d(a); // Private copy of "a"
...
}
...
void g(int k, int n)
{
h(k,a); // The global "a", not the private "a" in f
}
A.25 Példák a copyprivate adatattribútum záradékára
1. példa: A copyprivate záradék használatával az egyetlen szál által beszerzett értékeket közvetlenül a többi szál privát változóinak összes példányára továbbíthatja.
float x, y;
#pragma omp threadprivate(x, y)
void init( )
{
float a;
float b;
#pragma omp single copyprivate(a,b,x,y)
{
get_values(a,b,x,y);
}
use_values(a, b, x, y);
}
Ha a rutin init egy soros régióból van meghívva, az irányelvek jelenléte nem befolyásolja a viselkedését. Miután a get_values rutin hívását egy szál végrehajtotta, egyetlen szál sem hagyja el a szerkezetet, amíg az a,b, x és y által kijelölt privát objektumok az összes szálban az olvasott értékekkel meg nem határozódnak.
2. példa: Az előző példával ellentétben tegyük fel, hogy az olvasást egy adott szálnak kell elvégeznie, például a főszálnak. Ebben az esetben a copyprivate záradék nem használható közvetlenül a közvetítésre, de egy ideiglenes megosztott objektumhoz való hozzáférés biztosítására is használható.
float read_next( )
{
float * tmp;
float return_val;
#pragma omp single copyprivate(tmp)
{
tmp = (float *) malloc(sizeof(float));
}
#pragma omp master
{
get_float( tmp );
}
#pragma omp barrier
return_val = *tmp;
#pragma omp barrier
#pragma omp single
{
free(tmp);
}
return return_val;
}
3. példa: Tegyük fel, hogy a párhuzamos régióban szükséges zárolási objektumok száma nem határozható meg könnyen a beírás előtt. A copyprivate záradék használatával hozzáférést biztosíthat az adott párhuzamos régióban lefoglalt megosztott zárolási objektumokhoz.
#include <omp.h>
omp_lock_t *new_lock()
{
omp_lock_t *lock_ptr;
#pragma omp single copyprivate(lock_ptr)
{
lock_ptr = (omp_lock_t *) malloc(sizeof(omp_lock_t));
omp_init_lock( lock_ptr );
}
return lock_ptr;
}
A.26 A threadprivate irányelv
Az alábbi példák bemutatják, hogyan használható a threadprivate direktíva, hogy minden szálnak külön számlálót adjon.
1. példa
int counter = 0;
#pragma omp threadprivate(counter)
int sub()
{
counter++;
return(counter);
}
2. példa
int sub()
{
static int counter = 0;
#pragma omp threadprivate(counter)
counter++;
return(counter);
}
A.27 C99 változóhosszúságú tömbök
Az alábbi példa bemutatja, hogyan használható a C99 változóhosszúságú tömb (VLA) a firstprivate irányelvben.
Megjegyzés:
A változó hosszúságú tömbök jelenleg nem támogatottak a Visual C++-ban.
void f(int m, int C[m][m])
{
double v1[m];
...
#pragma omp parallel firstprivate(C, v1)
...
}
A.28 A num_threads záradék
Az alábbi példa a num_threads záradékot mutatja be. A párhuzamos régió legfeljebb 10 szálból áll.
#include <omp.h>
int main()
{
omp_set_dynamic(1);
...
#pragma omp parallel num_threads(10)
{
... parallel region ...
}
}
A.29 Munkamegosztási szerkezetek egy kritikus szerkezeten belül
Az alábbi példa egy szerkezeten belüli critical munkamegosztási szerkezet használatát mutatja be. Ez a példa azért megfelelő, mert a munkamegosztási szerkezet és a critical szerkezet nem kapcsolódik ugyanahhoz a párhuzamos régióhoz.
void f()
{
int i = 1;
#pragma omp parallel sections
{
#pragma omp section
{
#pragma omp critical (name)
{
#pragma omp parallel
{
#pragma omp single
{
i++;
}
}
}
}
}
}
A.30 Reprivatizáció
Az alábbi példa a változók reprivatizációját mutatja be. A privát változók újra megjelölhetők private beágyazott irányelvben. Ezeket a változókat nem szükséges megosztania a külső párhuzamos régióban.
int i, a;
...
#pragma omp parallel private(a)
{
...
#pragma omp parallel for private(a)
for (i=0; i<10; i++)
{
...
}
}
A.31 Szálbiztos zárolási függvények
Az alábbi C++ példa bemutatja, hogyan inicializálhat egy párhuzamos régióban lévő zárolási tömböt omp_init_lock használatával.
// A_13_omp_init_lock.cpp
// compile with: /openmp
#include <omp.h>
omp_lock_t *new_locks() {
int i;
omp_lock_t *lock = new omp_lock_t[1000];
#pragma omp parallel for private(i)
for (i = 0 ; i < 1000 ; i++)
omp_init_lock(&lock[i]);
return lock;
}
int main () {}