본문 바로가기

Programming/OpenMP

OpenMP 동기제어 지시어

OpenMP 동기제어 


멀티스레딩으로 프로그램을 제작할 때, 자주 발생하면서도 찾기 어려운 것이 동기화로 인해 발생하는 문제이다. OpenMP는 병렬영역에서 데이터 액세스 동기나 코드 실행의 동기를 제어하는 지시어를 지원한다.

master, atomic, critical, barrier, taskwait, flush, ordered 지시어가 있는데 각각 지시어들의 사용법과 특징을 알아본다.

 

1. master 지시어

스레드 팀에서 마스터 스레드에 의해서만 해당 코드가 실행되도록 한다.

#pragma omp master 지시어는 암시적 동기화를 지원하지 않기 때문에, 다른 스레드들은 이후에 나오는 코드를 수행하게 된다. 마스터 스레드의 처리 완료때 까지 대기하려면 barrier 지시어를 사용해야 한다.

 



2. barrier 지시어

스테드 팀의 모든 스레드가 동기화한다. 각 스레드는 스레드 팀의 다른 모든 스레드가 이 포인트에 도달할 때 까지 대기한다. 암시적 배리어를 제공하지 않는 지시어 또는 명시적으로 베리어를 기술하여 동기화를 확실히 할 필요가 있을 때 사용한다.

 

작업 중인 스레드가 완료될 때까지 barrier가 기술된 부분에서 모든 스레드가 대기하게 된다. 스레드 팀 내의 모든 스레드가 대기상태가 되면 다음 코드로 넘어가 실행을 시작하게 된다.

 

주의사항

1) 지시어는 C/C++ 언어 문법 규칙이 허용되는 곳에서만 사용할 수 있다. if, while, do, switch문 등의 직후에는 사용할 수 없다. barrier 지시어는 OpenMPfor, sections, single, critical 지시어 영역 내에 사용할 수 없다.

2) barrier 지시어를 작성한 경우, 스레드 팀의 모든 스레드가 barrier 지시어에 도달하게 해야 한다. 단 하나의 스레드라도 도달하지 못하면 프로그램은 barrier 지시어가 작성된 지점부터 이후의 코드를 실행할 수없다.


종료하지 않은 barrier 지시어

#pragma omp parallel

{

#pragma omp master

for( ; ; ) //무한 루프

#pragma omp barrier

printf(“ ”);

}


master 지시어가 지정한 마스터 스레드가 무한 루프에 들어가기 때문에, 다른 스레드는 barrier 지시어가 있는 지점에서 계속 기다린다.

 

 

 

3. taskwait 지시어

#pragma omp task 지시어로 작업 큐에 들어간 모든 코드가 완료될 때까지 #pragma omp task wait 지시어가 있는 위치에서 기다린다.

int a = 0; int b = 0; int c = 0;


#pragma omp parallel

{

#pragma omp task

a = work1();

#pragma omp task

b = work2();

#pragma omp taskwait

c = a+b;

}


c값을 구하기 위해서 a, b에 대한 작업이 완료된 이후에 c값을 계산하기 위해 taskwait 지시어를 사용하여 a, b에 대한 계산이 완료되기를 기다린다.

 

주의사항 task 지시어는 C/C++문법 규칙이 허용하는 곳에서만 사용할 수 있다. 하지만 if, while do, switch문 등의 직후에는 사용할 수 없다.

 

 

4. atomic 지시어

OpenMP 병렬영역에서 여러 개의 스레드가 하나의 공유 변수를 동시에 변경할 때 발생할 수 있는 오류(경쟁상태)를 방지한다. 공유 변수에 대해서, 읽기와 쓰기가 한 번에 이루어지는 연산자에서만 사용한다. atomic 지시어를 사용할 수 있는 연산식은 다음과 같다.

x 연산식= 상수 또는 계산식;

연산식에는 +, *, *, /, &, |, ^, <<, >>

상수 또는 계산식에는 변수 x가 올 수 없다.

 

문법

#pragma omp atomic

스칼라 연산식


atomic 지시어의 사용

int ThreadCount = 0;

#pragma omp parallel num_threads(10)

{

#pragma omp atomic

ThreadCount++;

}


 

주의사항

오른쪽 식에 변수 X 주의

중괄호 사용 X -> critical 사용




5. critical 지시어

#pragma omp critical 지시어 이후에 크리티컬 객체 이름을 지정하여 사용한다. 같은 크리티컬 객체명으로 지정된 영역은 한 번에 하나의 스레드만 실행된다. 다른 스레드가 같은 이름의 크리티컬 객체를 먼저 사용하고 있다면, 먼저 실행한 스레드가 크리티컬 영역의 동작이 완료될 때까지 진입 지점에서 대기한다.

 

문법

#pragma omp critical [(크리티컬 객체 이름)]

{//진입 지점

구조 블록

}//완료 지점


 

 #pragma omp critical(MAXVALUE)

{

if(max < Data[i])

max = Data[i];

}


 

주의사항

1) critical 지시어는 #pragma omp parallel로 지정된 영역의 스레드 팀뿐만 아니라, 프로그램 안의 모든 스레드의 동기 실행에 사용할 수 있다.

2) #pragma omp critical 지시어 영역 내에서는 for 지시어나 single 지시어를 사용할 수 없다.

3) C++에서는 critical 지시어 영역 내에서 throw된 예외는 같은 critical 영역에서 실행을 재개해야 한다. 그리고 throw된 예외는 같은 스레드가 catch해야 한다.

 




6. flush 지시어

멀티 스레드 환경에서 글로벌 메모리에 있는 변숫값을 참조할 때 일시적인 오류가 발생할 수 있다. 예를 들어 하나의 스레드가 글로벌 변수를 읽고, 동시에 다른 스레드가 같은 변수를 쓴다면, 프로그램이 어떻게 동작할지 예상할 수 없게 된다. 이처럼 하나의 메모리 변수를 가지고 여러 개의 스레드가 경합을 벌이게 될 때 그 정합성을 유지 시켜주는 지시어flush 이다. 스레드가 글로벌 메모리의 값을 읽을 때 임시적인 뷰를 가지게 된다. 그 이후 다른 스레드가 같은 변수에 대하여 값을 변경하면, 먼저 읽어간 스레드는 이전의 값을 가지고 연산을 수행하게 된다. 이때 값을 변경한 스레드에서 flush 지시어를 사용하면, 다른 스레드에서 읽어간 뷰가 갱신되면서 데이터의 정합성을 유지하게 된다.

 

문법

#pragma omp flush(변수명)

변수명에 지정되는 메모리의 값을 스레드 팀 전체에 전달하여, 그 데이터의 정합성을 유지하도록 함

변수는 콤마로 구분하여 여러개를 지정할수 있다.

변수 명을 지정하지 않은 flush지시어는 코드상에 있는 글로벌 변수를 전체 스레드에 변수값을 전달(flush)한다.

 

주의사항

1) 변수명에 포인터 변수가 지정되었을 경우, 포인터가 가리키는 데이터 값이 아니라 포인터 자신(주소 값)flush 된다.

2) OpenMP 병렬 dudddur에서 다음과 같은 암시적 flush 지점이 있다.

parallel, critical, ordered의 입구와 출구

nowait가 지정되지 않은 워크 쉐어 영역의 출구

태스크 스케줄 포인트의 직전과 직후

barrier 영역 안, omp_set_lock/omp_unset_lock로 지시 되는 영역, omp_test_lock/ omp_set_nest_lock/omp_unset_nest_lock/omp_test_nest_lock로 지정되는 영역

flush 변수명이 지정되었을 경우의, atomic 구문의 입구와 출구

 

예제 flush 사용

#include “stdafx.h”

#inlcude “omp.h”

 

int _tmain(int argc, _TCHAR* argv[])

{

    int a = 0, b = 0;

#pragma omp parallel sections

{

#pragma omp section

    {

        b=1;

#pragma omp flush(a, b)

        if(a==0)

            printf(“Thread 0 a : %d, b : %d\n”, a, b);

}

 

#pragma omp section

{

    a = 1;

#pragma omp flush(a, b)

if(b == 0)

            printf(“Thread 1 a : %d, b : %d\n”, a, b);

}

return 0;

}


2개의 스레드가 동시에 동작하게 될 때 공유 메모리에 대한 값을 참조할 때 개별 스레드는 임시적인 뷰를 가지고 있다. flush 구문이 없을 때 1번 스레드가 a의 값을 먼저 1로 변경하였더라도 0번 스레드는 임시적인 뷰로 인해 a의 값을 0으로 인식하고 if(a==0) 구문을 진입할 수 있다. 만일 진입하기 직전에 flush 지시어를 사용하게 되면 변수 ab에 대해 변경된 값이 있는지 다시 확인한 후에 다름 구문을 진행하게 된다.

 

 

 

7. order 지시어

OpenMP에서 루프 병렬영역에서 어떤 스레드가 먼저 실행될지는 정해져 있지 않다. 만일 인덱스 변수에 따른 순차 실행이 필요한 경우 #pragma omp ordered 지시어를 사용하면 된다. ordered 지시어를 사용할 경우 인덱스 카운터에 따라 0부터 오름차순으로 순차적으로 실행된다.

 

문법

#pragma omp for ordered

for (초기값; 조건식; 증감값)

{

#pragma omp ordered

구조블록

}


 

주의사항

1)#pragma omp ordered 지시어를 사용하기 위해서는 #pragma omp for 지시어 이후에 ordered 보조 지시어를 지정해야 한다. #pragma omp for 이후 ordered 보조 지시어를 지정하지 않으면 컴파일 에러가 난다.

2) #pragma omp for ordered와 같이 ordered 속성을 지정하여도 for 문 내에서 #pragma omp ordered 지시어를 사용하지 않으면 순차적으로 실행되지 않는다.

반응형
LIST