세마포어란 무엇인가?
세마포어(Semaphore)는 여러 프로세스나 스레드가 공유 자원에 동시에 접근하지 못하도록 제어하는 동기화(Synchronization) 도구입니다. 운영체제에서 매우 중요한 개념이며, 실제 프로그램에서 어떻게 활용되는지 이번 글에서 살펴보겠습니다.
POSIX 세마포어 준비하기
리눅스 시스템에서는 POSIX 세마포어 라이브러리를 기본으로 제공합니다. 이를 사용하려면 소스 코드에 semaphore.h 헤더 파일을 포함해야 하며, 컴파일할 때 아래와 같이 -lpthread와 -lrt 옵션을 함께 지정해야 합니다.
gcc program_name.c –lpthread -lrt
주요 함수 정리
- sem_init() : 이름 없는(unnamed) 세마포어를 초기화합니다. 주로 같은 프로세스 내부의 스레드 동기화에 사용됩니다.
- sem_open() : 이름 있는(named) 세마포어를 생성하거나 열어, 프로세스 간 통신(IPC) 환경에서 활용할 수 있습니다.
- sem_wait() : 세마포어 값을 1 감소시켜 잠금(Lock) 또는 대기(Wait) 상태에 진입합니다. 값이 0이라면 사용 가능해질 때까지 블록됩니다.
- sem_post() : 세마포어 값을 1 증가시켜 잠금을 해제하고, 대기 중인 다른 스레드나 프로세스에 진입 기회를 넘겨줍니다.
- sem_destroy() : 사용이 끝난 세마포어를 소멸시켜 자원을 정리합니다.
예제 코드
다음 예제는 두 개의 스레드가 하나의 세마포어를 공유하며, 임계 영역(Critical Section)에 한 번에 하나씩만 진입하도록 구현한 코드입니다.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t mutex;
void* thread(void* arg) { // 스레드처럼 동작하는 함수
sem_wait(&mutex); // 대기 상태 (잠금 획득)
printf("\nEntered into the Critical Section..\n");
sleep(3); // 임계 영역
printf("\nCompleted...\n"); // 임계 영역에서 벗어남
sem_post(&mutex); // 잠금 해제
}
main() {
sem_init(&mutex, 0, 1); // 세마포어 초기화 (초기값 1)
pthread_t th1, th2;
pthread_create(&th1, NULL, thread, NULL);
sleep(2);
pthread_create(&th2, NULL, thread, NULL);
// 작업 스레드들을 메인 스레드에 조인
pthread_join(th1, NULL);
pthread_join(th2, NULL);
sem_destroy(&mutex); // 세마포어 소멸
}
코드 흐름 설명
sem_init(&mutex, 0, 1)로 세마포어를 초기값 1로 설정합니다. 두 번째 인자가 0이면 같은 프로세스 내 스레드 간 공유를 의미합니다.- 첫 번째 스레드가
sem_wait()를 호출하면 세마포어 값이 0이 되어 임계 영역에 진입합니다. - 두 번째 스레드가
sem_wait()를 호출하면 값이 0이므로 대기 상태가 됩니다. - 첫 번째 스레드가
sem_post()로 잠금을 해제하면, 그제야 두 번째 스레드가 임계 영역에 진입할 수 있습니다.
컴파일 및 실행 결과
soumyadeep@soumyadeep-VirtualBox:~/Cpp_progs$ gcc 1270.posix_semaphore.c -lpthread -lrt
1270.posix_semaphore.c:19:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
main() {
^~~~
soumyadeep@soumyadeep-VirtualBox:~/Cpp_progs$ ./a.out
Entered into the Critical Section..
Completed...
Entered into the Critical Section..
Completed...
soumyadeep@soumyadeep-VirtualBox:~/Cpp_progs$
실행 결과 분석
출력을 보면 첫 번째 스레드가 "Entered into the Critical Section.."과 "Completed..."를 모두 출력하고 잠금을 해제한 이후에야 두 번째 스레드가 임계 영역에 진입한 것을 확인할 수 있습니다. 이것이 바로 세마포어가 두 스레드의 동시 접근을 차단하고 순차적인 실행을 보장하는 동기화 기능입니다.
참고로 컴파일 시 나타난 경고는 main() 함수의 반환 타입을 명시하지 않아 발생한 것입니다. int main()처럼 반환 타입을 명시적으로 선언하면 경고를 없앨 수 있으니 실제 프로젝트에서는 반드시 지정하는 것이 좋습니다.
정리
POSIX 세마포어는 sem_wait()로 잠그고 sem_post()로 푸는 간단한 인터페이스만으로 강력한 동기화를 제공합니다. 초기값을 1로 설정하면 뮤텍스(Mutex)처럼 상호 배제 용도로 사용할 수 있으며, 초기값을 N으로 설정하면 최대 N개의 스레드가 동시에 접근 가능한 카운팅 세마포어로도 활용할 수 있습니다.