세마포어

프로세스 / 쓰레드에서 공유 자원의 접근을 제어하는 정수형 변수입니다. 즉, 자원이 몇개 남았는지 나타내는 카운터입니다.
여러 스레드가 같은 변수 / 버퍼 / 장치 등에 동시에 접근하면 잘못된 결과를 낼 수 있기때문에 사용합니다.

구성요소

  1. 정수값(value)
    1. 현재 자원이 몇 개 남았는지를 나타냅니다. (value 가 3이면 동시에 3개의 쓰레드가 접근 가능)
  2. 대기 큐 (waiters list)
    1. 자원이 부족할 때 기다리는 스레드들을 담고 있습니다.

주요연산

  • P() 또는 wait() 또는 down()
    → 세마포어 값을 1 감소. 값이 0 이하라면 대기(block) 상태입니다. [sema_down()]
  • V() 또는 signal() 또는 up()
    → 세마포어 값을 1 증가. 대기 중인 스레드가 있으면 하나를 깨웁니다. [sema_up()]

특징

  • 음수 가능 → 대기 큐에 여러 스레드를 넣을 수 있습니다. (실제 세마포어값은 0인데, 대기열 수를 나타낸다)
  • 자원 개수 지정 가능 → 여러 개의 자원을 관리하는데 사용합니다.

방식(Busy-waiting, Block-wakeup)

1. Busy-waiting 방식

자원이 없을 때 계속 루프를 돌면서 대기하는 방식입니다.

while (sema.value == 0) {
    // 아무 일도 안 하고 기다림
}
// 자원 사용
sema.value--;

문제점

  • CPU 낭비: 아무 것도 안하면서 CPU를 점유합니다.
  • 멀티태스킹 환경에서 비효율적: 다른 스레드와 프로세스가 실행될 기회를 뺏습니다.

장점

  • 구현이 단순합니다.
  • context switch 없이 빠르게 재시도 가능합니다. (간단한 경우에는 빠르게 반응)

사용 예

  • 짧은 시간만 기다릴때 (스핀락)
  • 멀티코어 CPU에서 lock 경쟁이 짧을 때

2. Block-Wakeup 방식

자원이 없을 때 스레드를 block시키고, 자원이 생기면 wakeup시켜서 다시 실행되게 하는 방식입니다.

if (sema.value == 0) {
    block_this_thread();   // 스케줄러에게 CPU 반납
} else {
    sema.value--;
}

// sema_up()에서 자원이 생길시
sema.value++;
wakeup_one_waiter();  // 기다리던 스레드 중 하나 깨움

장점

  • CPU 낭비가 없음: 대기 스레드는 sleep 상태로 CPU를 점유하지 않습니다.
  • 멀티태스킹 환경 적합: 스케줄러가 다른 작업을 할 수 있습니다.

단점

  • 구현이 복잡하고 Context switch 비용이 듭니다.
  • 짧은 대기에는 오히려 느릴 수 있습니다.

사용 예

  • thread_block()으로 스레드를 막고, thread_unblock()으로 깨웁니다.
  • PintOS의 세마포어는 이 방식을 사용합니다.

PintOS에서

/* Pintos 세마포어 초기화 */
sema_init(&sema, 1);

/* 자원 획득 (down) */
sema_down(&sema);

/* 자원 해제 (up) */
sema_up(&sema);

Pintos의 thread/synch.c 에서 sema_down, sema_up 함수가 사용되며, 우선 순위 기아 현상 방지를 위해 보완하여야 합니다.

Pintos에서는 세마포어가 semaphore 구조체에 정의되어 있습니다.

profile
모든걸 기록하며 성장하고 싶은 개발자입니다. 현재 크래프톤 정글 8기를 수료하고 구직활동 중입니다.

0개의 댓글