Semaphore

세마포어(semaphore)는 공유 자원에 접근할 수 있는 프로세스의 수를 정해 접근을 제어하는 방법이다.
csProcessSemaphore
avatar
2025.05.17
·
9 min read

세마포어(semaphore)는 공유 자원에 접근할 수 있는 프로세스의 수를 정해 접근을 제어하는 방법이다. 세마포어의 작동 방식을 화장실 예에 비유하면 다음과 같다.

6182
  1. 식당에 화장실 세 칸과, 화장실 문을 열 수 있는 열쇠가 3개 있다.

  2. A가 열쇠 하나를 가지고 화장실에 간다. 열쇠는 2개가 남는다.

  3. B, C가 열쇠를 하나씩 가지고 화장실에 간다. 남은 열쇠가 없어서 D는 화장실에 가지 못하고 기다린다.

  4. C가 화장실에서 나와 열쇠를 돌려 놓으면, D가 화장실에 간다.

뮤텍스일 때와 동일하게 화장실은 공유 자원을 포함한 임계 영역을 의미하고 A, B, C, D는 공유 자원에 접근하려는 프로세스를 의미한다. 화장실 개수(열쇠 개수)는 공유 자원에 접근할 수 있는 프로세스의 수를 제어하기 위한 정수 변수를 나타낸다.

Mutex
뮤텍스(mutex)는 락(lock)을 가진 프로세스만이 공유 자원에 접근할 수 있게 하는 방법이다.
https://until.blog/@cs/-process--mutex
Mutex

세마포어는 임계 영역에 접근할 수 있는 키 n개를 지정하고 이 중 하나를 가진 프로세스만이 임계 영역에 접근하게 하는 방식이다. 이 방식은 공유 자원에 접근한 프로세스가 접근을 해제하면 다른 프로세스가 접근할 수 있도록 신호를 보낸다고 해서 시그널링 매커니즘(signaling mechanism)이라고도 한다.

Code Example (TypeScript)

// 세마포어 타입 정의
type Semaphore = {
  count: number;
  waitQueue: Array<(value: void | PromiseLike<void>) => void>;
  acquire: () => Promise<void>;
  release: () => void;
  getCount: () => number;
  getWaitQueueLength: () => number;
};

// 세마포어 생성 함수
function createSemaphore(initialCount: number): Semaphore {
  if (initialCount < 0) {
    throw new Error("세마포어 초기값은 0 이상이어야 합니다.");
  }
  
  const semaphore: Semaphore = {
    count: initialCount,
    waitQueue: [],
    
    // 자원 획득 함수 (화장실 열쇠 가져가기)
    acquire: async function(): Promise<void> {
      // 사용 가능한 자원이 있으면 즉시 획득
      if (this.count > 0) {
        this.count--;
        return;
      }

      // 사용 가능한 자원이 없으면 대기열에 추가하고 대기
      return new Promise<void>((resolve) => {
        this.waitQueue.push(resolve);
      });
    },
    
    // 자원 반환 함수 (화장실 열쇠 반납하기)
    release: function(): void {
      // 대기 중인 프로세스가 있으면 깨우기
      if (this.waitQueue.length > 0) {
        const nextProcess = this.waitQueue.shift();
        if (nextProcess) {
          nextProcess(); // 대기 중인 프로세스 깨우기
        }
      } else {
        // 대기 중인 프로세스가 없으면 자원 카운트 증가
        this.count++;
      }
    },
    
    // 현재 사용 가능한 자원 수 확인 함수
    getCount: function(): number {
      return this.count;
    },
    
    // 대기 중인 프로세스 수 확인 함수
    getWaitQueueLength: function(): number {
      return this.waitQueue.length;
    }
  };
  
  return semaphore;
}

/**
 * 화장실 사용 예제
 */
async function toiletExample() {
  const TOTAL_TOILETS = 3;  // 총 화장실 칸 수
  let usedToilets = 0;      // 현재 사용 중인 화장실 수
  
  // 화장실 세마포어 생성 (화장실 세 칸과 열쇠 세 개)
  const toiletSemaphore = createSemaphore(TOTAL_TOILETS);
  
  // 화장실 사용 함수
  async function useToilet(personName: string, usageTime: number): Promise<void> {
    console.log(`${personName}님이 화장실을 사용하려고 합니다.`);
    
    // 자원 획득 시도 (화장실 열쇠 가져가기)
    await toiletSemaphore.acquire();
    
    // 사용 중인 화장실 수 증가
    usedToilets++;
    const remainingToilets = TOTAL_TOILETS - usedToilets;
    
    console.log(`${personName}님이 화장실에 들어갔습니다. (남은 화장실: ${remainingToilets}칸)`);
    
    // 화장실 사용 중 (임계 영역 작업)
    await new Promise(resolve => setTimeout(resolve, usageTime));
    
    // 자원 반환 (화장실 열쇠 반납)
    toiletSemaphore.release();
    
    // 사용 중인 화장실 수 감소
    usedToilets--;
    const availableAfterExit = TOTAL_TOILETS - usedToilets;
    
    console.log(`${personName}님이 화장실에서 나왔습니다. (남은 화장실: ${availableAfterExit}칸)`);
    console.log(`대기 중인 사람: ${toiletSemaphore.getWaitQueueLength()}명`);
  }

  // 여러 사람이 동시에 화장실을 사용하려는 상황
  const people = [
    { name: '사람A', time: 2000 }, // 2초 동안 사용
    { name: '사람B', time: 1000 }, // 1초 동안 사용
    { name: '사람C', time: 3000 }, // 3초 동안 사용
    { name: '사람D', time: 1500 }, // 1.5초 동안 사용
    { name: '사람E', time: 2500 }  // 2.5초 동안 사용
  ];

  // 초기 상태 출력
  console.log(`초기 화장실 상태: 총 ${TOTAL_TOILETS}칸 사용 가능`);

  // 모든 사람이 동시에 화장실을 사용하려고 함
  await Promise.all(people.map(person => useToilet(person.name, person.time)));
  
  console.log("모든 사람이 화장실 사용을 마쳤습니다.");
}

// 예제 실행
toiletExample();

위 코드는 세마포어의 개념을 실제로 구현한 것이며 특징은 다음과 같습니다.

  1. 자원 카운트 관리: 세마포어는 사용 가능한 자원의 수를 카운트하는 정수값(count)을 가집니다. 뮤텍스와 달리 여러 개의 프로세스가 동시에 공유 자원에 접근할 수 있습니다.

  2. 자원 획득과 반환: acquire() 함수는 자원을 획득하는 과정이고, release() 함수는 자원을 반환하는 과정입니다. 자원을 획득하면 카운트가 감소하고, 반환하면 카운트가 증가합니다.

  3. 대기열 관리: 사용 가능한 자원이 없을 때, 프로세스는 대기열(waitQueue)에 추가되어 자원이 사용 가능해질 때까지 대기합니다.

  4. 비동기 처리: 자바스크립트/타입스크립트에서는 실제 스레드 기반 병렬 실행이 아닌 비동기 실행 모델을 사용하므로, Promise를 활용하여 자원 획득 대기를 구현했습니다.

이 구현은 실제 운영체제의 세마포어보다 단순화된 형태이지만, 기본 개념(여러 프로세스가 제한된 수의 공유 자원에 접근할 수 있도록 제어하는 것)을 명확하게 보여줍니다. 특히 화장실 예제를 통해 설명한 세마포어의 동작 방식을 코드로 직접 확인할 수 있습니다.

until-6184

실제 실행 시, 위 이미지와 같이 A, B, C, D, E가 동시에 화장실을 사용하려고 시도하지만, 세마포어로 인해 한 번에 세 사람만 화장실에 들어갈 수 있습니다. 네 번째와 다섯 번째 사람은 앞선 사람들 중 누군가가 화장실에서 나와 열쇠를 반납할 때까지 기다려야 합니다. 이것이 바로 공유 자원에 대한 접근을 제어하는 세마포어의 핵심 기능입니다.







- 컬렉션 아티클