🔹 1. 함수 개요
void eat(t_philo *philo)
- t_philo *philo: 현재 식사하려는 철학자의 정보를 담은 구조체 포인터
- 이 함수는 철학자가 포크를 집고 식사하는 과정을 처리합니다.
🔹 2. 코드 분석 (예제와 함께)
✅ 1. 오른쪽 포크를 집는다
pthread_mutex_lock(philo->r_fork);
print_message("has taken a fork", philo, philo->id);
🔹 개념
- pthread_mutex_lock(philo->r_fork); → 오른쪽 포크(뮤텍스)를 잠금.
- print_message("has taken a fork", philo, philo->id); → 철학자가 포크를 집었다는 메시지를 출력.
📌 예제
철학자가 오른쪽 포크를 집는 과정:
[Philosopher 2] has taken a fork
✅ 2. 철학자가 혼자 있는 경우 (1명일 때)
if (philo->num_of_philos == 1)
{
ft_usleep(philo->time_to_die);
pthread_mutex_unlock(philo->r_fork);
return ;
}
🔹 개념
- 철학자가 혼자만 있을 경우, 왼쪽 포크를 절대 집을 수 없으므로 무조건 죽음.
- ft_usleep(philo->time_to_die); → 철학자가 기다렸다가 죽음.
- pthread_mutex_unlock(philo->r_fork); → 사용했던 포크(뮤텍스)를 반환.
📌 예제
철학자가 혼자만 있을 경우:
[Philosopher 1] has taken a fork
[Philosopher 1] died after waiting time_to_die
✅ 3. 왼쪽 포크를 집는다
pthread_mutex_lock(philo->l_fork);
print_message("has taken a fork", philo, philo->id);
🔹 개념
- pthread_mutex_lock(philo->l_fork); → 왼쪽 포크(뮤텍스) 잠금.
- print_message("has taken a fork", philo, philo->id); → 철학자가 두 번째 포크를 집었음을 출력.
📌 예제
[Philosopher 2] has taken a fork
[Philosopher 2] has taken a fork
✅ 4. 식사를 시작한다
philo->eating = 1;
print_message("is eating", philo, philo->id);
🔹 개념
- philo->eating = 1; → 현재 철학자가 식사 중임을 표시.
- print_message("is eating", philo, philo->id); → 콘솔에 "철학자가 식사 중" 출력.
📌 예제
[Philosopher 2] is eating
✅ 5. 식사 상태를 기록
pthread_mutex_lock(philo->meal_lock);
philo->last_meal = get_current_time();
philo->meals_eaten++;
pthread_mutex_unlock(philo->meal_lock);
🔹 개념
- 임계 구역 보호:
- pthread_mutex_lock(philo->meal_lock); → 식사 데이터(last_meal, meals_eaten) 보호
- philo->last_meal = get_current_time(); → 마지막으로 식사한 시간 업데이트
- philo->meals_eaten++; → 식사 횟수 증가
- pthread_mutex_unlock(philo->meal_lock); → 잠금 해제
📌 왜 meal_lock을 사용해야 할까?
- 철학자가 식사하는 동안 다른 스레드(감시자)가 last_meal을 읽거나 변경할 수 있기 때문.
- meal_lock이 없으면 경쟁 상태(Race Condition) 발생.
✅ 6. 식사 시간만큼 대기
ft_usleep(philo->time_to_eat);
🔹 개념
- ft_usleep(philo->time_to_eat); → 식사 시간이 끝날 때까지 대기.
📌 예제
철학자의 식사 시간이 2초(time_to_eat = 2000ms)라면:
[Philosopher 2] is eating # 대기 (2초)
✅ 7. 식사 완료 후 상태 변경
philo->eating = 0;
🔹 개념
- philo->eating = 0; → 철학자가 식사 완료 상태로 변경.
📌 이유?
- 감시자 스레드가 철학자가 먹고 있는지 판단할 때 사용.
✅ 8. 포크(뮤텍스)를 반납
pthread_mutex_unlock(philo->l_fork);
pthread_mutex_unlock(philo->r_fork);
🔹 개념
- 왼쪽 포크 반납
- 오른쪽 포크 반납
📌 이유?
- 다른 철학자들이 포크를 사용할 수 있도록 해줘야 함.
🔹 3. 최종 실행 흐름 (예제)
철학자 2명이 eat()을 실행한다고 가정하면:
[Philosopher 1] has taken a fork
[Philosopher 1] has taken a fork
[Philosopher 1] is eating
[Philosopher 2] has taken a fork
[Philosopher 2] has taken a fork
[Philosopher 2] is eating
🔹 4. 전체 코드 정리
void eat(t_philo *philo)
{
// 오른쪽 포크 집기
pthread_mutex_lock(philo->r_fork);
print_message("has taken a fork", philo, philo->id);
// 철학자가 혼자 있는 경우 (바로 죽음)
if (philo->num_of_philos == 1)
{
ft_usleep(philo->time_to_die);
pthread_mutex_unlock(philo->r_fork);
return;
}
// 왼쪽 포크 집기
pthread_mutex_lock(philo->l_fork);
print_message("has taken a fork", philo, philo->id);
// 식사 시작
philo->eating = 1;
print_message("is eating", philo, philo->id);
// 식사 시간 기록 (임계 구역 보호)
pthread_mutex_lock(philo->meal_lock);
philo->last_meal = get_current_time();
philo->meals_eaten++;
pthread_mutex_unlock(philo->meal_lock);
// 식사 시간만큼 대기
ft_usleep(philo->time_to_eat);
// 식사 완료
philo->eating = 0;
// 포크 반납
pthread_mutex_unlock(philo->l_fork);
pthread_mutex_unlock(philo->r_fork);
}
🔹 5. 정리
단계 코드 설명
1️⃣ | pthread_mutex_lock(philo->r_fork); | 오른쪽 포크 잡기 |
2️⃣ | if (philo->num_of_philos == 1) {...} | 철학자가 혼자면 죽음 |
3️⃣ | pthread_mutex_lock(philo->l_fork); | 왼쪽 포크 잡기 |
4️⃣ | philo->eating = 1; | 식사 시작 |
5️⃣ | pthread_mutex_lock(philo->meal_lock); | 식사 정보 업데이트 |
6️⃣ | ft_usleep(philo->time_to_eat); | 식사 시간 동안 대기 |
7️⃣ | philo->eating = 0; | 식사 완료 |
8️⃣ | pthread_mutex_unlock(philo->l_fork); pthread_mutex_unlock(philo->r_fork); | 포크 반납 |
🚀 즉, 이 함수는 철학자가 포크를 집고, 식사하고, 다시 포크를 내려놓는 과정 전체를 담당하는 핵심 코드입니다!
네, pthread_mutex_lock(philo->l_fork);는 철학자가 왼쪽 포크(l_fork)를 집는 행동 자체를 의미합니다.
🔹 왜 그렇죠?
- pthread_mutex_t 뮤텍스는 동기화 도구로, 여러 스레드가 동시에 공유 자원(이 경우, 포크)을 사용하지 못하도록 합니다.
- pthread_mutex_lock(philo->l_fork);을 호출하면:
- 뮤텍스가 잠겨있지 않다면, 즉시 잠그고(포크를 집고) 다음 코드 실행.
- 뮤텍스가 이미 잠겨 있다면, 다른 철학자가 포크를 사용 중이므로 잠금이 해제될 때까지 대기.
📌 철학자의 행동 흐름
pthread_mutex_lock(philo->l_fork); // 왼쪽 포크를 집는다
print_message("has taken a fork", philo, philo->id);
🚀 이 코드는 철학자가 왼쪽 포크를 실제로 집는 동작을 의미하며,
포크를 성공적으로 집은 후 "has taken a fork" 메시지를 출력합니다.
🔹 예제: 동작 흐름
만약 철학자가 2명이고, 동시에 eat()을 실행한다고 가정해봅시다.
✅ 철학자 1이 포크를 집는 과정
- 철학자 1이 오른쪽 포크(1번 포크)를 집음 → pthread_mutex_lock(philo->r_fork);
- 철학자 1이 왼쪽 포크(2번 포크)를 집음 → pthread_mutex_lock(philo->l_fork);
- 철학자 1이 "has taken a fork" 메시지를 출력하고 식사 시작.
[Philosopher 1] has taken a fork
[Philosopher 1] has taken a fork
[Philosopher 1] is eating
✅ 철학자 2가 포크를 집으려 하면?
- 철학자 2도 오른쪽 포크(2번 포크)를 집으려 시도 → 철학자 1이 이미 사용 중이므로 대기!
pthread_mutex_lock(philo->r_fork); // 🚫 철학자 1이 사용 중 -> 블록됨
- 철학자 2는 pthread_mutex_lock(philo->r_fork);에서 멈춰 있음.
- 철학자 1이 식사를 끝내고 포크를 내려놓아야 철학자 2가 진행 가능.
🔹 결론
✅ pthread_mutex_lock(philo->l_fork);은 철학자가 왼쪽 포크를 집는 행동 자체를 의미합니다.
✅ 뮤텍스 잠금이 완료될 때까지 기다리므로, 실제로 포크를 집을 수 있을 때만 코드가 진행됩니다.
✅ pthread_mutex_unlock(philo->l_fork);을 호출해야 다른 철학자가 이 포크를 사용할 수 있습니다.
pthread_mutex_lock(philo->l_fork);을 실행할 때, 해당 포크(뮤텍스)가 이미 다른 철학자에 의해 사용 중이라면, 현재 철학자는 해당 뮤텍스가 해제될 때까지 기다립니다.
🔹 1. pthread_mutex_lock()의 동작 방식
pthread_mutex_lock(mutex)의 기본 동작:
- 뮤텍스가 잠겨있지 않다면, 즉시 잠그고 다음 코드 실행.
- 뮤텍스가 이미 잠겨 있다면, 다른 쓰레드(철학자)가 pthread_mutex_unlock()을 호출할 때까지 블로킹(대기).
📌 즉, 철학자는 포크가 해제될 때까지 무한정 기다리게 됩니다.
🔹 2. 예제 상황
🎭 철학자 2명이 있다고 가정
- 철학자 1(Philo 1): 포크 1️⃣(오른쪽), 포크 2️⃣(왼쪽) 사용 중.
- 철학자 2(Philo 2): 포크 2️⃣(오른쪽), 포크 3️⃣(왼쪽) 사용하려 함.
// 철학자 1이 오른쪽 포크를 집는다
pthread_mutex_lock(philo1->r_fork); // 성공 (포크 1️⃣ 잡음)
// 철학자 1이 왼쪽 포크를 집는다
pthread_mutex_lock(philo1->l_fork); // 성공 (포크 2️⃣ 잡음)
// 철학자 2가 오른쪽 포크를 집는다
pthread_mutex_lock(philo2->r_fork); // 🚫 포크 2️⃣를 기다림 (철학자 1이 사용 중)
📌 철학자 2는 포크 2️⃣(오른쪽)가 해제될 때까지 pthread_mutex_lock(philo2->r_fork);에서 대기함.
🔹 3. 데드락(교착 상태) 문제
만약 모든 철학자가 한 손에만 포크를 집고 다른 포크를 기다린다면, 서로가 포크를 반납하지 않아 영원히 기다리는 데드락(Deadlock) 상태에 빠질 수 있습니다.
❌ 데드락 발생 예시
- 철학자 1이 fork 1️⃣을 잡고 fork 2️⃣를 기다림.
- 철학자 2가 fork 2️⃣를 잡고 fork 3️⃣를 기다림.
- 철학자 3이 fork 3️⃣를 잡고 fork 1️⃣을 기다림.
- 모두가 기다리는 상태 → 데드락 발생!
✅ 해결 방법
- 비대칭 잠금 순서 사용
- 짝수 번호 철학자는 왼쪽 → 오른쪽 순서로 포크를 잡음.
- 홀수 번호 철학자는 오른쪽 → 왼쪽 순서로 포크를 잡음.
- 이러면 모든 철학자가 동시에 같은 방향으로 포크를 잡지 않게 됨 → 데드락 방지!
- 타임아웃 방식 (pthread_mutex_trylock()) 사용
- pthread_mutex_trylock()을 사용하여 즉시 잠글 수 없으면 다른 행동(예: 잠시 대기 후 재시도) 수행.
- 예제:
if (pthread_mutex_trylock(philo->l_fork) == 0) { // 성공적으로 포크를 집었을 때만 진행 } else { // 포크를 못 집으면 일정 시간 기다렸다가 다시 시도 usleep(100); }
- 하나의 철학자를 제외하고 나머지가 동시에 식사하게끔 조정
- 예: 철학자 0번만 포크를 먼저 잡고 기다리는 방식으로 설계하면 데드락을 피할 수 있음.
🔹 4. 결론
✅ 다른 철학자가 포크를 잡고 있으면 pthread_mutex_lock()은 잠금이 해제될 때까지 기다린다.
✅ 무한정 기다리는 구조는 데드락을 유발할 수 있으므로, 비대칭 방식 또는 pthread_mutex_trylock()을 사용하면 좋다.
✅ 철학자의 식사 문제를 해결할 때, 포크 잡는 순서를 조절하면 데드락을 방지할 수 있다. 🚀
'C Language' 카테고리의 다른 글
공유 뮤텍스를 사용하는 이유와 예시 (0) | 2025.03.14 |
---|---|
mutex 입금문제 (2) | 2025.03.13 |
pthread_mutex_init NULL 초기화 (0) | 2025.03.13 |
뮤텍스 포인터 차이 (0) | 2025.03.13 |
mutex 잠금 원리 (0) | 2025.03.13 |