본문 바로가기

C Language

mutex

주어진 구조체 `t_philo`와 `t_program`은 철학자 문제(Dining Philosophers Problem)를 해결하기 위해 설계된 데이터 구조입니다. 각 구조체의 멤버 변수와 역할을 상세히 설명하겠습니다.

---

### **1. `t_philo` 구조체**
이 구조체는 각 철학자(philosopher)의 상태와 정보를 저장합니다. 철학자는 스레드로 구현되며, 각 철학자는 자신의 상태를 관리하고 다른 철학자와 공유 자원(포크)을 사용합니다.

#### **멤버 변수 설명**
1. **`pthread_t thread`**:
   - 철학자의 스레드를 나타냅니다. 각 철학자는 독립적인 스레드로 실행됩니다.

2. **`int id`**:
   - 철학자의 고유 ID입니다. 예를 들어, 철학자가 5명이라면 ID는 1부터 5까지입니다.

3. **`int eating`**:
   - 철학자가 현재 먹고 있는지 여부를 나타내는 플래그입니다.
   - `1`: 먹는 중, `0`: 먹지 않는 중.

4. **`int meals_eaten`**:
   - 철학자가 먹은 식사 횟수를 기록합니다. 모든 철학자가 지정된 횟수만큼 먹으면 프로그램이 종료됩니다.

5. **`size_t last_meal`**:
   - 철학자가 마지막으로 식사를 한 시간을 기록합니다. 이 값은 철학자가 죽었는지 확인하는 데 사용됩니다.

6. **`size_t time_to_die`**:
   - 철학자가 죽기까지의 시간입니다. 이 시간 동안 식사를 하지 않으면 철학자는 죽습니다.

7. **`size_t time_to_eat`**:
   - 철학자가 식사를 하는 데 걸리는 시간입니다.

8. **`size_t time_to_sleep`**:
   - 철학자가 자는 데 걸리는 시간입니다.

9. **`size_t start_time`**:
   - 프로그램이 시작된 시간을 기록합니다. 모든 시간 계산은 이 값을 기준으로 합니다.

10. **`int num_of_philos`**:
    - 철학자의 총 수입니다.

11. **`int num_times_to_eat`**:
    - 철학자가 식사를 해야 하는 횟수입니다. 모든 철학자가 이 횟수만큼 먹으면 프로그램이 종료됩니다.

12. **`int *dead`**:
    - 철학자가 죽었는지 여부를 나타내는 플래그입니다. 이 값은 모든 철학자가 공유합니다.
    - `1`: 철학자가 죽음, `0`: 철학자가 살아있음.

13. **`pthread_mutex_t *r_fork`**:
    - 철학자의 오른쪽에 있는 포크를 나타내는 뮤텍스입니다.

14. **`pthread_mutex_t *l_fork`**:
    - 철학자의 왼쪽에 있는 포크를 나타내는 뮤텍스입니다.

15. **`pthread_mutex_t *write_lock`**:
    - 출력(print)을 동기화하기 위한 뮤텍스입니다. 여러 스레드가 동시에 출력하지 않도록 합니다.

16. **`pthread_mutex_t *dead_lock`**:
    - `dead` 플래그를 보호하기 위한 뮤텍스입니다. 철학자의 죽음 상태를 안전하게 업데이트하기 위해 사용됩니다.

17. **`pthread_mutex_t *meal_lock`**:
    - `last_meal`과 `meals_eaten`을 보호하기 위한 뮤텍스입니다. 철학자의 식사 상태를 안전하게 업데이트하기 위해 사용됩니다.

---

### **2. `t_program` 구조체**
이 구조체는 프로그램 전체의 상태와 공유 자원을 관리합니다. 모든 철학자가 공유하는 데이터와 뮤텍스들이 포함됩니다.

#### **멤버 변수 설명**
1. **`int dead_flag`**:
   - 프로그램에서 철학자가 죽었는지 여부를 나타내는 플래그입니다. 이 값은 모든 철학자가 공유합니다.
   - `1`: 철학자가 죽음, `0`: 철학자가 살아있음.

2. **`pthread_mutex_t dead_lock`**:
   - `dead_flag`를 보호하기 위한 뮤텍스입니다. 철학자의 죽음 상태를 안전하게 업데이트하기 위해 사용됩니다.

3. **`pthread_mutex_t meal_lock`**:
   - 철학자의 식사 상태(`last_meal`, `meals_eaten`)를 보호하기 위한 뮤텍스입니다.

4. **`pthread_mutex_t write_lock`**:
   - 출력(print)을 동기화하기 위한 뮤텍스입니다. 여러 스레드가 동시에 출력하지 않도록 합니다.

5. **`t_philo *philos`**:
   - 모든 철학자의 정보를 저장하는 배열입니다. 각 철학자는 `t_philo` 구조체로 표현됩니다.

---

### **예시: 철학자 문제에서의 동작**
1. **초기화**:
   - `t_program` 구조체를 초기화하고, `dead_flag`, 뮤텍스, 철학자 배열(`philos`)을 설정합니다.
   - 각 철학자(`t_philo`)의 ID, 포크, 시간 등을 초기화합니다.

2. **스레드 생성**:
   - 각 철학자에 대해 스레드를 생성합니다. 스레드는 철학자의 행동(생각, 먹기, 자기)을 시뮬레이션합니다.

3. **철학자의 행동**:
   - 철학자는 왼쪽 포크와 오른쪽 포크를 잡고, 먹은 후 포크를 놓습니다.
   - 철학자는 `time_to_eat` 동안 먹고, `time_to_sleep` 동안 잠을 잡니다.
   - 철학자는 `last_meal` 시간을 업데이트하고, `meals_eaten`을 증가시킵니다.

4. **죽음 확인**:
   - 메인 스레드 또는 모니터링 스레드가 주기적으로 철학자의 `last_meal` 시간을 확인합니다.
   - `last_meal` 시간이 `time_to_die`를 초과하면 철학자를 죽음 상태로 표시하고 프로그램을 종료합니다.

5. **종료 조건**:
   - 모든 철학자가 `num_times_to_eat`만큼 먹으면 프로그램이 종료됩니다.
   - 철학자가 죽으면 프로그램이 종료됩니다.

---

### **예시 코드**
```c
void *philosopher_routine(void *arg)
{
    t_philo *philo = (t_philo *)arg;

    while (1)
    {
        // 생각하기
        print_status(philo, "is thinking");

        // 포크 잡기
        pthread_mutex_lock(philo->l_fork);
        print_status(philo, "has taken a fork");
        pthread_mutex_lock(philo->r_fork);
        print_status(philo, "has taken a fork");

        // 먹기
        print_status(philo, "is eating");
        usleep(philo->time_to_eat * 1000);

        // 포크 놓기
        pthread_mutex_unlock(philo->l_fork);
        pthread_mutex_unlock(philo->r_fork);

        // 자기
        print_status(philo, "is sleeping");
        usleep(philo->time_to_sleep * 1000);
    }
    return (NULL);
}
```

---

### **결론**
- `t_philo`는 각 철학자의 상태와 정보를 관리합니다.
- `t_program`은 프로그램 전체의 상태와 공유 자원을 관리합니다.
- 철학자 문제는 스레드와 뮤텍스를 사용하여 철학자의 행동을 시뮬레이션하고, 교착 상태와 기아를 방지합니다.

 

`t_program` 구조체는 철학자 문제(Dining Philosophers Problem)에서 **프로그램 전체의 상태와 공유 자원을 관리**하는 데 사용됩니다. 이 구조체는 모든 철학자(`t_philo`)가 공유하는 데이터와 동기화를 위한 뮤텍스들을 포함하고 있습니다. 아래에서 `t_program`의 각 멤버 변수의 의미와 `t_philo`와의 관계를 상세히 설명하겠습니다.

---

### **`t_program` 구조체의 멤버 변수 설명**

1. **`int dead_flag`**:
   - **의미**: 프로그램에서 철학자가 죽었는지 여부를 나타내는 플래그입니다.
   - **역할**: 모든 철학자가 이 플래그를 공유합니다. 한 철학자가 죽으면 이 값이 `1`로 설정되고, 프로그램이 종료됩니다.
   - **예시**: 철학자 3번이 `time_to_die` 시간 동안 먹지 못하면 `dead_flag`가 `1`로 설정됩니다.

2. **`pthread_mutex_t dead_lock`**:
   - **의미**: `dead_flag`를 보호하기 위한 뮤텍스입니다.
   - **역할**: 여러 스레드(철학자)가 동시에 `dead_flag`를 읽거나 수정하지 못하도록 동기화합니다.
   - **예시**: 철학자 3번이 죽었을 때, `dead_lock`을 사용하여 `dead_flag`를 안전하게 `1`로 설정합니다.

3. **`pthread_mutex_t meal_lock`**:
   - **의미**: 철학자의 식사 상태(`last_meal`, `meals_eaten`)를 보호하기 위한 뮤텍스입니다.
   - **역할**: 여러 스레드가 동시에 철학자의 식사 상태를 업데이트하지 못하도록 동기화합니다.
   - **예시**: 철학자 2번이 먹기를 마쳤을 때, `meal_lock`을 사용하여 `meals_eaten`을 안전하게 증가시킵니다.

4. **`pthread_mutex_t write_lock`**:
   - **의미**: 출력(print)을 동기화하기 위한 뮤텍스입니다.
   - **역할**: 여러 스레드가 동시에 출력하지 못하도록 합니다. 출력이 섞이는 것을 방지합니다.
   - **예시**: 철학자 1번이 "is eating"을 출력할 때, 다른 철학자가 동시에 출력하지 못하도록 `write_lock`을 사용합니다.

5. **`t_philo *philos`**:
   - **의미**: 모든 철학자의 정보를 저장하는 배열입니다.
   - **역할**: 각 철학자는 `t_philo` 구조체로 표현되며, 이 배열을 통해 모든 철학자의 상태를 관리합니다.
   - **예시**: 철학자 5명이 있다면 `philos[0]`부터 `philos[4]`까지 각 철학자의 정보가 저장됩니다.

---

### **`t_program`과 `t_philo`의 관계**
- **`t_program`**:
  - 프로그램 전체의 상태와 공유 자원을 관리합니다.
  - 모든 철학자가 공유하는 데이터(`dead_flag`, 뮤텍스 등)를 포함합니다.
  - 철학자들의 상태를 모니터링하고 동기화하는 역할을 합니다.

- **`t_philo`**:
  - 각 철학자의 개별 상태와 정보를 관리합니다.
  - 철학자의 ID, 먹은 횟수, 마지막 식사 시간 등을 저장합니다.
  - 철학자의 행동(생각, 먹기, 자기)을 시뮬레이션합니다.

---

### **예시: `t_program`과 `t_philo`의 상호작용**

1. **초기화**:
   - `t_program` 구조체를 초기화하고, `dead_flag`를 `0`으로 설정합니다.
   - `dead_lock`, `meal_lock`, `write_lock` 뮤텍스를 초기화합니다.
   - `philos` 배열을 할당하고, 각 철학자(`t_philo`)를 초기화합니다.

   ```c
   t_program program;
   program.dead_flag = 0;
   pthread_mutex_init(&program.dead_lock, NULL);
   pthread_mutex_init(&program.meal_lock, NULL);
   pthread_mutex_init(&program.write_lock, NULL);
   program.philos = malloc(sizeof(t_philo) * num_of_philos);
   ```

2. **철학자 스레드 생성**:
   - 각 철학자에 대해 스레드를 생성하고, 철학자의 행동을 시뮬레이션합니다.

   ```c
   for (int i = 0; i < num_of_philos; i++)
   {
       pthread_create(&program.philos[i].thread, NULL, philosopher_routine, &program.philos[i]);
   }
   ```

3. **철학자의 행동**:
   - 철학자는 `t_program`의 뮤텍스를 사용하여 공유 자원에 안전하게 접근합니다.
   - 예를 들어, 철학자가 죽었는지 확인할 때 `dead_lock`을 사용합니다.

   ```c
   void *philosopher_routine(void *arg)
   {
       t_philo *philo = (t_philo *)arg;

       while (1)
       {
           // 죽음 확인
           pthread_mutex_lock(&philo->program->dead_lock);
           if (philo->program->dead_flag == 1)
           {
               pthread_mutex_unlock(&philo->program->dead_lock);
               break;
           }
           pthread_mutex_unlock(&philo->program->dead_lock);

           // 철학자의 행동 (생각, 먹기, 자기)
       }
       return (NULL);
   }
   ```

4. **모니터링**:
   - 메인 스레드 또는 별도의 모니터링 스레드가 주기적으로 철학자의 상태를 확인합니다.
   - 철학자가 죽으면 `dead_flag`를 `1`로 설정하고 프로그램을 종료합니다.

   ```c
   void monitor(t_program *program)
   {
       while (1)
       {
           for (int i = 0; i < program->num_of_philos; i++)
           {
               pthread_mutex_lock(&program->meal_lock);
               if (현재 시간 - program->philos[i].last_meal > program->philos[i].time_to_die)
               {
                   pthread_mutex_lock(&program->dead_lock);
                   program->dead_flag = 1;
                   pthread_mutex_unlock(&program->dead_lock);
                   return;
               }
               pthread_mutex_unlock(&program->meal_lock);
           }
       }
   }
   ```

---

### **결론**
- `t_program`은 프로그램 전체의 상태와 공유 자원을 관리합니다.
- `t_philo`는 각 철학자의 개별 상태와 정보를 관리합니다.
- `t_program`과 `t_philo`는 철학자 문제를 해결하기 위해 서로 밀접하게 상호작용합니다. `t_program`은 공유 데이터와 동기화를 제공하고, `t_philo`는 철학자의 행동을 시뮬레이션합니다.

 

`pthread_mutex_t *write_lock;`과 `pthread_mutex_t write_lock;`은 뮤텍스를 선언하는 두 가지 다른 방식입니다. 이 두 방식은 메모리 관리와 접근 방식에서 차이가 있으며, 철학자 문제(Dining Philosophers Problem)에서 각각 다른 목적으로 사용됩니다. 아래에서 두 선언 방식의 차이와 사용 이유를 상세히 설명하겠습니다.

---

### **1. `pthread_mutex_t write_lock;`**
- **의미**: 뮤텍스를 스택(stack)에 선언합니다.
- **특징**:
  - 뮤텍스는 해당 구조체(`t_program` 또는 `t_philo`)의 메모리 공간 내에 직접 저장됩니다.
  - 뮤텍스의 생명주기는 해당 구조체의 생명주기와 동일합니다.
  - 별도의 메모리 할당 또는 해제가 필요하지 않습니다.
- **사용 이유**:
  - 뮤텍스가 하나의 구조체에서만 사용될 때 적합합니다.
  - 메모리 관리가 간단하고, 초기화와 해제가 쉽습니다.
- **예시**:
  ```c
  typedef struct s_program
  {
      pthread_mutex_t write_lock; // 뮤텍스를 스택에 선언
  } t_program;

  int main()
  {
      t_program program;
      pthread_mutex_init(&program.write_lock, NULL); // 뮤텍스 초기화
      // 뮤텍스 사용
      pthread_mutex_destroy(&program.write_lock); // 뮤텍스 해제
      return 0;
  }
  ```

---

### **2. `pthread_mutex_t *write_lock;`**
- **의미**: 뮤텍스를 힙(heap)에 동적으로 할당합니다.
- **특징**:
  - 뮤텍스는 포인터로 선언되며, 실제 메모리는 힙에 할당됩니다.
  - 뮤텍스의 생명주기는 프로그래머가 직접 관리해야 합니다.
  - `malloc` 또는 `calloc`을 사용하여 메모리를 할당하고, `free`를 사용하여 해제해야 합니다.
- **사용 이유**:
  - 뮤텍스가 여러 구조체 또는 스레드에서 공유되어야 할 때 적합합니다.
  - 뮤텍스의 주소를 쉽게 공유할 수 있습니다.
  - 동적 메모리 할당을 통해 유연성을 높일 수 있습니다.
- **예시**:
  ```c
  typedef struct s_program
  {
      pthread_mutex_t *write_lock; // 뮤텍스를 포인터로 선언
  } t_program;

  int main()
  {
      t_program program;
      program.write_lock = malloc(sizeof(pthread_mutex_t)); // 뮤텍스 메모리 할당
      pthread_mutex_init(program.write_lock, NULL); // 뮤텍스 초기화
      // 뮤텍스 사용
      pthread_mutex_destroy(program.write_lock); // 뮤텍스 해제
      free(program.write_lock); // 메모리 해제
      return 0;
  }
  ```

---

### **두 방식의 차이점**
| **구분**               | `pthread_mutex_t write_lock;`                     | `pthread_mutex_t *write_lock;`                  |
|------------------------|--------------------------------------------------|------------------------------------------------|
| **메모리 위치**         | 스택(stack)                                      | 힙(heap)                                       |
| **생명주기**           | 구조체의 생명주기와 동일                         | 프로그래머가 직접 관리                         |
| **메모리 관리**         | 간단함 (할당/해제 필요 없음)                     | 복잡함 (`malloc`, `free` 필요)                 |
| **공유 가능성**         | 한 구조체 내에서만 사용                          | 여러 구조체 또는 스레드에서 공유 가능          |
| **유연성**             | 낮음                                             | 높음                                           |

---

### **철학자 문제에서의 사용 이유**
철학자 문제에서는 `pthread_mutex_t *write_lock;`과 같은 포인터 방식을 주로 사용합니다. 그 이유는 다음과 같습니다:

1. **공유 필요성**:
   - `write_lock`은 모든 철학자(`t_philo`)가 공유해야 합니다. 여러 철학자가 동시에 출력하지 못하도록 동기화하기 위해 사용됩니다.
   - 포인터를 사용하면 모든 철학자가 동일한 뮤텍스를 참조할 수 있습니다.

2. **유연성**:
   - 뮤텍스를 동적으로 할당하면 프로그램의 구조를 유연하게 설계할 수 있습니다.
   - 예를 들어, 철학자의 수가 동적으로 결정되는 경우에도 쉽게 대응할 수 있습니다.

3. **일관성**:
   - 모든 철학자가 동일한 뮤텍스를 사용하도록 강제할 수 있습니다. 이는 출력의 일관성을 보장합니다.

---

### **예시: 철학자 문제에서의 사용**
```c
typedef struct s_philo
{
    pthread_mutex_t *write_lock; // 출력용 뮤텍스 (포인터로 선언)
} t_philo;

typedef struct s_program
{
    pthread_mutex_t *write_lock; // 출력용 뮤텍스 (포인터로 선언)
    t_philo *philos;
} t_program;

int main()
{
    t_program program;

    // 뮤텍스 메모리 할당 및 초기화
    program.write_lock = malloc(sizeof(pthread_mutex_t));
    pthread_mutex_init(program.write_lock, NULL);

    // 철학자 초기화
    program.philos = malloc(sizeof(t_philo) * num_of_philos);
    for (int i = 0; i < num_of_philos; i++)
    {
        program.philos[i].write_lock = program.write_lock; // 모든 철학자가 동일한 뮤텍스를 공유
    }

    // 철학자 스레드 생성 및 실행

    // 뮤텍스 해제 및 메모리 정리
    pthread_mutex_destroy(program.write_lock);
    free(program.write_lock);
    free(program.philos);

    return 0;
}
```

---

### **결론**
- `pthread_mutex_t write_lock;`은 뮤텍스를 스택에 선언하며, 간단한 경우에 적합합니다.
- `pthread_mutex_t *write_lock;`은 뮤텍스를 힙에 동적으로 할당하며, 여러 스레드나 구조체에서 공유해야 할 때 적합합니다.
- 철학자 문제에서는 뮤텍스를 공유해야 하므로 `pthread_mutex_t *write_lock;`과 같은 포인터 방식을 사용합니다.

'C Language' 카테고리의 다른 글

pthread_mutex_lock unlock  (0) 2025.03.10
mutex null 속성으로 초기화 + fork  (0) 2025.03.10
cpp 01  (0) 2025.03.10
minishell01  (0) 2025.03.08
read 함수 설명  (0) 2025.02.27