본문 바로가기

C Language

eat 함수

 


🔹 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);을 호출하면:
    1. 뮤텍스가 잠겨있지 않다면, 즉시 잠그고(포크를 집고) 다음 코드 실행.
    2. 뮤텍스가 이미 잠겨 있다면, 다른 철학자가 포크를 사용 중이므로 잠금이 해제될 때까지 대기.

📌 철학자의 행동 흐름

pthread_mutex_lock(philo->l_fork);  // 왼쪽 포크를 집는다
print_message("has taken a fork", philo, philo->id);

🚀 이 코드는 철학자가 왼쪽 포크를 실제로 집는 동작을 의미하며,
포크를 성공적으로 집은 후 "has taken a fork" 메시지를 출력합니다.


🔹 예제: 동작 흐름

만약 철학자가 2명이고, 동시에 eat()을 실행한다고 가정해봅시다.

철학자 1이 포크를 집는 과정

  1. 철학자 1이 오른쪽 포크(1번 포크)를 집음 → pthread_mutex_lock(philo->r_fork);
  2. 철학자 1이 왼쪽 포크(2번 포크)를 집음 → pthread_mutex_lock(philo->l_fork);
  3. 철학자 1이 "has taken a fork" 메시지를 출력하고 식사 시작.
[Philosopher 1] has taken a fork
[Philosopher 1] has taken a fork
[Philosopher 1] is eating

철학자 2가 포크를 집으려 하면?

  1. 철학자 2도 오른쪽 포크(2번 포크)를 집으려 시도 → 철학자 1이 이미 사용 중이므로 대기!
    pthread_mutex_lock(philo->r_fork);  // 🚫 철학자 1이 사용 중 -> 블록됨
    
  2. 철학자 2는 pthread_mutex_lock(philo->r_fork);에서 멈춰 있음.
  3. 철학자 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)의 기본 동작:

  1. 뮤텍스가 잠겨있지 않다면, 즉시 잠그고 다음 코드 실행.
  2. 뮤텍스가 이미 잠겨 있다면, 다른 쓰레드(철학자)가 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️⃣을 기다림.
  • 모두가 기다리는 상태 → 데드락 발생!

✅ 해결 방법

  1. 비대칭 잠금 순서 사용
    • 짝수 번호 철학자는 왼쪽 → 오른쪽 순서로 포크를 잡음.
    • 홀수 번호 철학자는 오른쪽 → 왼쪽 순서로 포크를 잡음.
    • 이러면 모든 철학자가 동시에 같은 방향으로 포크를 잡지 않게 됨 → 데드락 방지!
  2. 타임아웃 방식 (pthread_mutex_trylock()) 사용
    • pthread_mutex_trylock()을 사용하여 즉시 잠글 수 없으면 다른 행동(예: 잠시 대기 후 재시도) 수행.
    • 예제:
      if (pthread_mutex_trylock(philo->l_fork) == 0)
      {
          // 성공적으로 포크를 집었을 때만 진행
      }
      else
      {
          // 포크를 못 집으면 일정 시간 기다렸다가 다시 시도
          usleep(100);
      }
      
  3. 하나의 철학자를 제외하고 나머지가 동시에 식사하게끔 조정
    • 예: 철학자 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