세마포어란 무엇일까?

박승우·2024년 5월 30일

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

이 키워드는 세번에 걸쳐서 알아볼 것이다.

세마포어가 뭐에요?


세마포어(Semaphore)는 멀티스레드 환경에서 스레드 간의 동기화를 위해 사용되는 기법이다.
세마포어는 변수와 두 가지 원자적 연산으로 구성되며, 이를 통해 여러 스레드가 자원에 접근할 때
발생할 수 있는 경합 조건을 방지한다.

Semaphore의 구성

세마포어 S는 정수값을 가지는 변수이며, 다음과 같이 P와 V라는 명령에 의해서만 접근할 수 있다.

P는 크리티컬 섹션 에 들어가기 전에 수행되고, V는 크리티컬 섹션에서 나올 때 수행된다. 이때 변수 값을 수정하는 연산은 모두 원자성을 만족해야 한다.
다시 말해, 한 프로세스(또는 스레드)에서 세마포어 값을 변경하는 동안 다른 프로세스가 동시에 이 값을 변경해서는 안 된다.

기본 개념

- 변수 (semaphore): 정수 값을 가지며, 자원의 상태를 나타냅니다.
- Wait (P) 연산: 세마포어 값을 감소시키는 연산으로, 값이 0인 경우 대기합니다.
- Signal (V) 연산: 세마포어 값을 증가시키는 연산으로, 대기 중인 스레드가 있으면 깨웁니다.

Semaphore의 종류

계수 세마포어

계수 세마포어(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;
}

Semaphore의 단점 및 약점

  1. P함수와 V함수의 동작은 독립적이기 때문에 잘못 사용하는 경우 문제가 발생한다.
  • P - 임계 구역 - P : 현재 프로세스가 임계 구역에서 빠져나갈 수 없게 된다. 또한 다른 프로세스들은 임계 구역에 들어갈 수 없으므로 교착 상태(Deadlock)가 발생한다.
  • V - 임계 구역 - P : 2개 이상의 프로세스가 동시에 임계구역에 들어갈 수 있으므로 상호 배제(Mutual Exclusion)를 보장할 수 없게 된다.
  1. 고급 언어에서 동기화를 제공해야 한다.

  2. 교착 상태 (Deadlock)

  • 여러 스레드가 서로가 소유한 리소스를 기다리면서 무한 대기에 빠질 수 있다.
    이를 방지하기 위해 리소스 요청 순서를 정하거나 타임아웃을 설정할 수 있다.
  1. 기아 상태 (Starvation)
  • 특정 스레드가 리소스를 계속 기다리면서 실행되지 못하는 상황이다.
    우선순위 조정이나 공정한 스케줄링을 통해 해결할 수 있다.
  1. 버지 웨이트 (Busy Wait)
  • 세마포어가 다른 스레드에 의해 사용 중일 때 스레드가 반복적으로 상태를 확인하며
    CPU를 소비 하는 상황이다.이를 방지하기 위해 블록킹 메커니즘을 사용한다.

Semaphore의 Monitor 알고리즘

모니터(Monitor)는 고수준의 동기화 추상화로, 세마포어와 조건 변수를 함께 사용하여 상호 배제와 조건 동기화를 제공한다.
모니터는 한 번에 하나의 스레드만 접근할 수 있는 공유 자원과 관련된 여러 연산들을 포함한다.

모니터의 두 가지 주요 구성 요소

  1. 상호 배제(Mutual Exclusion): 모니터에 진입하는 스레드는 하나만 존재할 수 있다. 이는 세마포어를 통해 구현할 수 있다.
  2. 조건 변수(Condition Variable): 특정 조건이 만족될 때까지 스레드를 대기시키고, 조건이 만족되면 대기 중인 스레드를 깨우는 메커니즘을 제공한다.

결론 - 느낀 점

있었던 작업이 있어서 오랜만에 키워드를 작성하는데 확실히 이론으로만 공부하니 쉽게 와닿지
않는 개념이었다. 스레드를 배웠지만 스레드하나만으로 다양한 기술이 들어가고 이론이 들어가는
것이 참으로 신기하지만 배움에 미학이 들어나는 키워드였다. 정리를 했지만 더욱 공부해서
이해를 하고 넘어가야 할 것 같다.

profile
게임을 좋아하는 사람 입니다!

0개의 댓글