ecole42

philosophers

philosophers는 ecole42의 프로젝트로, 식사하는 철학자 문제를 세 가지 다른 동기화 메커니즘(mutex, semaphore, process)으로 해결하는 프로젝트. 동시성 프로그래밍에서 자원 공유와 데드락 방지를 다루는 고전적인 문제를 통해 스레드, 프로세스, 동기화 기법 등 시스템 프로그래밍의 핵심 개념을 학습한다

C

개요

식사하는 철학자 문제는 동시성 프로그래밍에서 자원 공유와 데드락 방지를 다루는 고전적인 문제다. 이 프로젝트는 세 가지 다른 동기화 메커니즘을 사용하여 이 문제를 해결한다:

  1. philo_one: pthread와 mutex를 사용한 구현
  2. philo_two: pthread와 semaphore를 사용한 구현
  3. philo_three: 프로세스(fork)와 semaphore를 사용한 구현

프로젝트 구조

philosophers/
├── philo_one/      # pthread + mutex 버전
├── philo_two/      # pthread + semaphore 버전
└── philo_three/    # process + semaphore 버전

각 디렉토리는 독립적인 프로젝트로 구성되어 있으며, 각각의 Makefile을 가지고 있다.

각 버전별 특징

philo_one

  • 동기화 메커니즘: pthread mutex
  • 특징:
    • 각 포크에 대해 개별 mutex 사용
    • 홀수/짝수 철학자를 분리하여 데드락 방지
    • 각 철학자마다 모니터링 스레드 생성
    • pickupputdown mutex로 포크 집기/놓기 동작 동기화

philo_two

  • 동기화 메커니즘: pthread + semaphore
  • 특징:
    • named semaphore를 사용하여 포크 관리
    • semaphore를 통한 동시성 제어
    • 각 철학자마다 개별 eating semaphore 사용
    • pickupputdown semaphore로 포크 집기/놓기 동작 동기화

philo_three

  • 동기화 메커니즘: process (fork) + semaphore
  • 특징:
    • 각 철학자가 별도의 프로세스로 실행
    • 프로세스 간 통신을 위해 named semaphore 사용
    • 부모 프로세스가 자식 프로세스들을 관리
    • 철학자가 죽으면 모든 프로세스를 종료

빌드 및 실행

빌드

각 디렉토리에서 개별적으로 빌드할 수 있다:

# philo_one 빌드
cd philo_one
make

# philo_two 빌드
cd philo_two
make

# philo_three 빌드
cd philo_three
make

정리

# 오브젝트 파일 삭제
make clean

# 실행 파일까지 삭제
make fclean

# 재빌드
make re

사용법

실행 형식

./philo_[one|two|three] number_of_philosophers time_to_die time_to_eat time_to_sleep [number_of_times_each_philosopher_must_eat]

매개변수 설명

  1. number_of_philosophers: 철학자의 수 (최소 2명)
  2. time_to_die: 밀리초 단위. 철학자가 마지막 식사 후 이 시간 동안 식사를 하지 않으면 죽는다
  3. time_to_eat: 밀리초 단위. 철학자가 식사하는 데 걸리는 시간
  4. time_to_sleep: 밀리초 단위. 철학자가 잠을 자는 데 걸리는 시간
  5. number_of_times_each_philosopher_must_eat (선택): 각 철학자가 최소한 먹어야 하는 식사 횟수. 이 매개변수가 제공되면 모든 철학자가 이 횟수만큼 식사를 완료하면 시뮬레이션이 종료된다.

실행 예제

# 기본 실행 (5명의 철학자, 800ms 내에 식사하지 않으면 죽음, 200ms 식사, 200ms 수면)
./philo_one 5 800 200 200

# 각 철학자가 7번씩 식사해야 하는 경우
./philo_one 5 800 200 200 7

# 다른 버전 실행
./philo_two 4 410 200 200
./philo_three 3 400 200 200 5

출력 형식

프로그램은 다음과 같은 형식으로 상태를 출력한다:

[timestamp_in_ms] [philosopher_number] [status]

상태 종류:

  • is thinking: 철학자가 생각 중
  • has taken a fork: 포크를 집음
  • is eating: 식사 중
  • is sleeping: 잠자는 중
  • died: 죽음

예시:

0 1 is thinking
0 2 is thinking
200 1 has taken a fork
200 1 has taken a fork
200 1 is eating
400 1 is sleeping

프로젝트 구조 상세

공통 파일 구조

각 버전은 다음과 같은 파일 구조를 가지고 있다:

  • main.c: 프로그램 진입점, 인자 파싱, 초기화
  • philo.c: 철학자 로직 구현
  • print.c: 상태 출력 함수
  • error.c: 에러 처리 함수
  • utils_1.c, utils_2.c: 유틸리티 함수들
  • inc/philosophers.h: 메인 헤더 파일
  • inc/struct.h: 데이터 구조 정의

주요 데이터 구조

t_philo (철학자 구조체)

typedef struct s_philo
{
    int             index;          // 철학자 인덱스
    pthread_t       thread;         // 메인 스레드
    pthread_t       mthread;        // 모니터링 스레드 (philo_one, philo_two)
    int             num_eat;        // 식사 횟수
    unsigned long   start_time;     // 시작 시간
    unsigned long   last_eat_time;  // 마지막 식사 시간
} t_philo;

t_global (전역 구조체)

각 버전마다 약간씩 다르지만, 공통적으로 포함하는 필드:

  • num_philo: 철학자 수
  • time_to_die, time_to_eat, time_to_sleep: 시간 설정
  • num_must_eat: 필수 식사 횟수
  • philos: 철학자 배열
  • 동기화 객체들 (mutex 또는 semaphore)

동기화 메커니즘 비교

버전동기화 방식장점단점
philo_oneMutex간단하고 직관적많은 mutex 필요
philo_twoSemaphore유연한 동시성 제어Named semaphore 관리 필요
philo_threeProcess + Semaphore프로세스 격리프로세스 관리 오버헤드

주의사항

  1. Named Semaphore 정리: philo_twophilo_three는 named semaphore를 사용하므로, 프로그램이 비정상 종료되면 semaphore가 남을 수 있다. 필요시 수동으로 정리해야 한다:

    # macOS/Linux에서 semaphore 확인 및 정리
    ipcs -s  # semaphore 목록 확인
    ipcrm -s <semid>  # 특정 semaphore 삭제
    
  2. 타이밍: 프로그램은 usleep()을 사용하여 시간을 제어하므로, 실제 시간은 시스템 부하에 따라 약간 다를 수 있다.

  3. 데드락 방지: 각 버전은 서로 다른 방식으로 데드락을 방지한다:

    • philo_one: 홀수/짝수 철학자 분리 및 pickup mutex 사용
    • philo_two: pickup semaphore 사용
    • philo_three: pickup semaphore 사용