식사하는 철학자 문제는 Dijkstra가 제안한 동기화 문제의 고전입니다. 데드락의 4가지 조건을 직관적으로 이해할 수 있는 최고의 예제입니다.


문제 설명

원형 테이블에 5명의 철학자가 앉아 있습니다. 각 철학자 사이에 포크가 하나씩 있고, 식사하려면 양쪽 포크를 모두 들어야 합니다.

식사하는 철학자 문제 다이어그램

PLAINTEXT
        P0
      /    \
   F4      F0
    /        \
  P4          P1
   \          /
   F3      F1
     \    /
      P3—P2
        F2

P = 철학자, F = 포크

철학자는 두 가지 행동을 반복합니다:

  1. 생각하기 (Think) — 포크 불필요
  2. 식사하기 (Eat) — 양쪽 포크 2개 모두 필요

데드락이 발생하는 단순 구현

C
semaphore fork[5] = {1, 1, 1, 1, 1};

void philosopher(int i) {
    while (true) {
        think();
        wait(fork[i]);           // 왼쪽 포크 집기
        wait(fork[(i+1) % 5]);   // 오른쪽 포크 집기
        eat();
        signal(fork[i]);
        signal(fork[(i+1) % 5]);
    }
}

데드락 시나리오: 5명 모두 동시에 왼쪽 포크를 집으면, 아무도 오른쪽 포크를 못 집고 영원히 대기합니다.

PLAINTEXT
P0: fork[0] 잡음, fork[1] 대기
P1: fork[1] 잡음, fork[2] 대기
P2: fork[2] 잡음, fork[3] 대기
P3: fork[3] 잡음, fork[4] 대기
P4: fork[4] 잡음, fork[0] 대기 ← 원형 대기!

데드락 4가지 조건이 모두 충족됩니다:

  1. 상호 배제: 포크는 한 사람만 사용 가능
  2. 점유 대기: 왼쪽 포크를 잡은 채 오른쪽을 대기
  3. 비선점: 다른 사람의 포크를 뺏을 수 없음
  4. 원형 대기: P0→P1→P2→P3→P4→P0 순환

해결 방법 1: 비대칭 순서 (원형 대기 깨기)

마지막 철학자만 포크를 집는 순서를 바꿉니다.

C
void philosopher(int i) {
    while (true) {
        think();
        if (i == 4) {
            // 마지막 철학자는 오른쪽 먼저
            wait(fork[(i+1) % 5]);
            wait(fork[i]);
        } else {
            // 나머지는 왼쪽 먼저
            wait(fork[i]);
            wait(fork[(i+1) % 5]);
        }
        eat();
        signal(fork[i]);
        signal(fork[(i+1) % 5]);
    }
}

원형 대기 조건이 깨져서 데드락이 발생하지 않습니다.


해결 방법 2: 동시 식사 인원 제한

동시에 식사할 수 있는 철학자 수를 4명으로 제한합니다.

C
semaphore seats = 4;  // 최대 4명만 동시 식사 시도

void philosopher(int i) {
    while (true) {
        think();
        wait(seats);               // 자리가 있을 때만 시도
        wait(fork[i]);
        wait(fork[(i+1) % 5]);
        eat();
        signal(fork[i]);
        signal(fork[(i+1) % 5]);
        signal(seats);
    }
}

5명 중 4명만 동시에 포크를 잡으려 하면, 최소 한 명은 양쪽 포크를 모두 잡을 수 있습니다.


해결 방법 3: 모니터 사용

C
enum { THINKING, HUNGRY, EATING } state[5];
condition self[5];
mutex monitor_lock;

void pickup(int i) {
    lock(monitor_lock);
    state[i] = HUNGRY;
    test(i);                // 양쪽 이웃이 안 먹고 있으면 EATING으로 전환
    if (state[i] != EATING)
        wait(self[i]);      // 먹을 수 없으면 대기
    unlock(monitor_lock);
}

void putdown(int i) {
    lock(monitor_lock);
    state[i] = THINKING;
    test((i+4) % 5);        // 왼쪽 이웃 깨움
    test((i+1) % 5);        // 오른쪽 이웃 깨움
    unlock(monitor_lock);
}

void test(int i) {
    if (state[(i+4)%5] != EATING &&
        state[i] == HUNGRY &&
        state[(i+1)%5] != EATING) {
        state[i] = EATING;
        signal(self[i]);     // 대기 중이면 깨움
    }
}

양쪽 포크를 원자적으로 확인하고 집는 방식으로 데드락을 방지합니다.


해결 방법 비교

방법깨는 조건장점단점
비대칭 순서원형 대기간단비대칭이라 직관적이지 않음
인원 제한점유 대기간단동시성 약간 감소
모니터점유 대기기아 방지 가능구현 복잡
trylock + 양보비선점유연라이브락 가능성

핵심 포인트

  • 왜 데드락이 발생하는가: 4가지 조건(상호 배제, 점유 대기, 비선점, 원형 대기)이 모두 충족
  • 가장 간단한 해결법: 포크 집는 순서를 통일 (번호가 낮은 포크부터) → 원형 대기 깨짐
  • 실제 적용: 여러 자원을 순서 없이 잡으면 데드락 위험 → 자원에 번호를 매기고 순서대로 획득

정리

식사하는 철학자 문제는 데드락을 이해하는 가장 직관적인 모델입니다. 핵심은 데드락 4가지 조건 중 하나를 깨면 해결 된다는 것이고, 실제로는 "자원 획득 순서를 통일"하는 것이 가장 흔한 해결책입니다.

댓글 로딩 중...