자 서른 네 번째 키워드인 세마포어를 알아 볼 것이다.
이 키워드는 세번에 걸쳐서 알아볼 것이다.

세마포어(Semaphore)는 멀티스레드 환경에서 스레드 간의 동기화를 위해 사용되는 기법이다.
세마포어는 변수와 두 가지 원자적 연산으로 구성되며, 이를 통해 여러 스레드가 자원에 접근할 때
발생할 수 있는 경합 조건을 방지한다.
세마포어 S는 정수값을 가지는 변수이며, 다음과 같이 P와 V라는 명령에 의해서만 접근할 수 있다.
P는 크리티컬 섹션 에 들어가기 전에 수행되고, V는 크리티컬 섹션에서 나올 때 수행된다. 이때 변수 값을 수정하는 연산은 모두 원자성을 만족해야 한다.
다시 말해, 한 프로세스(또는 스레드)에서 세마포어 값을 변경하는 동안 다른 프로세스가 동시에 이 값을 변경해서는 안 된다.
- 변수 (semaphore): 정수 값을 가지며, 자원의 상태를 나타냅니다.
- Wait (P) 연산: 세마포어 값을 감소시키는 연산으로, 값이 0인 경우 대기합니다.
- Signal (V) 연산: 세마포어 값을 증가시키는 연산으로, 대기 중인 스레드가 있으면 깨웁니다.
계수 세마포어(counting semaphore)에서는 초기값은 가능한 자원의 수로 정해지며, 세마포어 값의 범위는 정해져 있지 않다.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;
sem_t empty_slots;
sem_t full_slots;
sem_t mutex;
void* producer(void* arg) {
for (int i = 0; i < 10; i++) {
sem_wait(&empty_slots); // 빈 슬롯 대기
sem_wait(&mutex); // 크리티컬 섹션 진입
buffer[count++] = i; // 데이터 생산
printf("Produced: %d\n", i);
sem_post(&mutex); // 크리티컬 섹션 종료
sem_post(&full_slots); // 가득 찬 슬롯 증가
}
return NULL;
}
void* consumer(void* arg) {
for (int i = 0; i < 10; i++) {
sem_wait(&full_slots); // 가득 찬 슬롯 대기
sem_wait(&mutex); // 크리티컬 섹션 진입
int item = buffer[--count]; // 데이터 소비
printf("Consumed: %d\n", item);
sem_post(&mutex); // 크리티컬 섹션 종료
sem_post(&empty_slots); // 빈 슬롯 증가
}
return NULL;
}
int main() {
pthread_t producer_thread, consumer_thread;
sem_init(&empty_slots, 0, BUFFER_SIZE);
sem_init(&full_slots, 0, 0);
sem_init(&mutex, 0, 1);
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
sem_destroy(&empty_slots);
sem_destroy(&full_slots);
sem_destroy(&mutex);
return 0;
}
이진 세마포어(binary semaphore)에서는 세마포어 값으로 0 또는 1을 가진다. 계수 세마포어보다 간단히 구현할 수 있으며, Test and Set 등 하드웨어가 지원하는 기능을 이용하여 구현하기도 한다. 또한, 이진 세마포어를 이용하여 계수 세마포어를 구현할 수도 있다.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t binary_semaphore;
void* critical_section(void* arg) {
sem_wait(&binary_semaphore); // 진입
// 크리티컬 섹션 코드
printf("Thread %ld in critical section.\n", (long)arg);
sem_post(&binary_semaphore); // 종료
return NULL;
}
int main() {
pthread_t threads[2];
sem_init(&binary_semaphore, 0, 1); // 초기값 1
for (long i = 0; i < 2; i++) {
pthread_create(&threads[i], NULL, critical_section, (void*)i);
}
for (int i = 0; i < 2; i++) {
pthread_join(threads[i], NULL);
}
sem_destroy(&binary_semaphore);
return 0;
}
고급 언어에서 동기화를 제공해야 한다.
교착 상태 (Deadlock)
모니터(Monitor)는 고수준의 동기화 추상화로, 세마포어와 조건 변수를 함께 사용하여 상호 배제와 조건 동기화를 제공한다.
모니터는 한 번에 하나의 스레드만 접근할 수 있는 공유 자원과 관련된 여러 연산들을 포함한다.
모니터의 두 가지 주요 구성 요소
있었던 작업이 있어서 오랜만에 키워드를 작성하는데 확실히 이론으로만 공부하니 쉽게 와닿지
않는 개념이었다. 스레드를 배웠지만 스레드하나만으로 다양한 기술이 들어가고 이론이 들어가는
것이 참으로 신기하지만 배움에 미학이 들어나는 키워드였다. 정리를 했지만 더욱 공부해서
이해를 하고 넘어가야 할 것 같다.