18-5 쓰레드 동기화 - 세마포어(Semaphore) [C][LINUX]
세마포어는 18-4에서 다룬 뮤텍스와 유사합니다. 여기서는 0과 1만을 사용하는 바이너리 세마포어라는 것을 대상으로 쓰레드의 실행순서 컨트롤 중심의 동기화를 다룹니다.
#include<semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
//success:0 fail:0 이외의 값
위 두 함수는 세마포어의 생성 및 소멸에 관한 함수입니다.
sem에는 세마포어의 참조 값 저장을 위한 변수의 주소 값 전달(소멸 시에는 소멸시키려는 세마포어의 참조 값을 저장하고 있는 변수의 주소 값 전달),
pshared에는 0 전달 시 하나의 프로세스 내에서 접근 가능, 0 이외의 값 전달 시 둘 이상의 프로세스에 의해 접근 가능한 세마포어를 생성하는데 여기서는 쓰레드의 동기화를 목적으로 하므로 0을 전달하고,
value에는 생성되는 세마포어의 초기 값을 지정합니다.
#include<semaphore.h>
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
//success: 0 fail: 0 이외의 값
sem_post에 전달된 세마포어 값은 1 증가하고, sem_wait에 전달된 세마포어 값은 1 감소합니다.세마포어 값은 0보다 작아질 수 없기에 세마포어 값이 0 일때 sem_wait이 호출되면 블로킹 상태에 놓입니다.
즉, sem_post가 unlock, sem_wait가 lock 역할을 하게 됩니다.
sem_wait(&sem);
...
sem_post(&sem);
위와 같이 임계영역을 감싸서 사용할 수 있습니다.
다음은 접근 순서의 동기화와 관련된 예제 시나리오입니다.
쓰레드 A가 전역변수 num에 값을 저장하면 쓰레드 B가 이 값을 가져다가 누적시킨다.
위 시나리오대로 프로그램을 구현하려면 변수 num은 쓰레드 A - 쓰레드 B 순서로 이뤄져야 합니다. 이를 구현하기 위해선 세마포어가 2개 필요합니다.
//전역 변수
static sem_t sem_one, sem_two;
static int num;
//main 함수
...
sem_init(&sem_one,0,0); // 세마포어 초기값 0
sem_init(&sem_two,0,1); // 세마포어 초기값 1
...
// 쓰레드 A
...
for(i=0; i<5; i++){
sem_wait(&sem_two);
num=value;
sem_post(&sem_one);
}
...
// 쓰레드 B
...
for(i=0; i<5; i++){
sem_wait(&sem_one);
sum+=num;
sem_post(&sem_two);
}
...
sem_one의 초기값은 0, sem_two의 초기값은 1입니다. 따라서 처음에 쓰레드 A의 sem_wait(&sem_two)는 반환되고 sem_two는 0이 됩니다. num에 값이 입력되면 sem_one이 1이 됨으로써 쓰레드B의 sem_wait(&sem_one)은 반환되며 sem_one은 다시 0이 됩니다. 쓰레드A가 다시 만난 sem_wait(&sem_two)는 블로킹 됩니다. 쓰레드B에서 sum+=num 연산이 끝나면 sem_two는 1증가하여 블로킹된 쓰레드A의 sem_wait함수가 반환됩니다. 동시에 쓰레드B는 sem_wait함수로 블로킹됩니다. 이 과정이 반복됩니다.