본문 바로가기

C Language

mutex 잠금 원리

pthread_mutex_lock(philo->dead_lock);을 사용하는 이유는 *philo->dead 변수에 대한 동기화를 보장하기 위해서입니다. 여러 개의 스레드가 동시에 *philo->dead 값을 읽거나 수정할 수 있기 때문에, 이를 보호하지 않으면 경쟁 상태(Race Condition)가 발생할 수 있습니다.


🔹 동기화의 필요성

예를 들어, 두 개의 철학자 스레드(스레드 A와 스레드 B)가 있다고 가정해봅시다. 이들은 *philo->dead 값을 읽거나 변경할 수 있습니다.

만약 pthread_mutex_lock()을 사용하지 않는다면 다음과 같은 문제가 발생할 수 있습니다.

if (*philo->dead == 1)  // 스레드 A가 이 값을 읽는 순간, 스레드 B가 동시에 0에서 1로 변경할 수 있음.
{
    return 1;  // 스레드 A는 아직 0인 줄 알고 다음 코드로 넘어갈 수도 있음.
}

즉, 스레드 A가 *philo->dead 값을 확인하는 순간, 스레드 B가 이를 수정하면 잘못된 동작이 발생할 수 있습니다.


🔹 뮤텍스를 통한 보호

다음은 pthread_mutex_lock()을 사용하여 보호하는 방식입니다.

int dead_loop(t_philo *philo)
{
    pthread_mutex_lock(philo->dead_lock); // 🔒 뮤텍스 잠금
    if (*philo->dead == 1)  // 🔹 보호된 영역: 다른 스레드가 값을 변경하지 못함
    {
        pthread_mutex_unlock(philo->dead_lock); // 🔓 뮤텍스 해제
        return 1;
    }
    pthread_mutex_unlock(philo->dead_lock); // 🔓 뮤텍스 해제
    return 0;
}

📌 뮤텍스 보호의 원리

  1. 잠금(pthread_mutex_lock)
    • 스레드가 philo->dead 값에 접근하기 전에 먼저 philo->dead_lock을 잠급니다.
    • 다른 스레드는 philo->dead_lock이 해제될 때까지 기다려야 합니다.
  2. 변수 접근 (*philo->dead)
    • 이제 안전하게 *philo->dead 값을 읽거나 수정할 수 있습니다.
    • 다른 스레드가 동시에 값을 변경하는 것을 방지할 수 있습니다.
  3. 해제 (pthread_mutex_unlock)
    • philo->dead 값을 확인한 후에는 philo->dead_lock을 해제하여 다른 스레드가 접근할 수 있도록 합니다.

🔹 경쟁 상태 방지를 위한 예제

뮤텍스를 사용하지 않았을 때 (문제 발생 가능)

void *philosopher(void *arg)
{
    t_philo *philo = (t_philo *)arg;

    while (1)
    {
        if (*philo->dead == 1) // 🚨 동시에 여러 스레드가 접근할 가능성이 있음 (Race Condition)
        {
            printf("Philosopher %d stops\n", philo->id);
            break;
        }
        // 철학자가 생각하고 식사하는 코드...
    }
    return NULL;
}

위 코드에서는 여러 개의 철학자 스레드가 *philo->dead 값을 동시에 읽을 수 있기 때문에, 한 스레드가 값을 변경하는 순간 다른 스레드가 예기치 않은 동작을 할 수 있습니다.


뮤텍스를 사용한 경우 (안전한 동작)

void *philosopher(void *arg)
{
    t_philo *philo = (t_philo *)arg;

    while (1)
    {
        pthread_mutex_lock(philo->dead_lock); // 🔒 뮤텍스 잠금
        if (*philo->dead == 1) // 🚀 다른 스레드가 값을 변경할 수 없음
        {
            pthread_mutex_unlock(philo->dead_lock); // 🔓 뮤텍스 해제
            printf("Philosopher %d stops\n", philo->id);
            break;
        }
        pthread_mutex_unlock(philo->dead_lock); // 🔓 뮤텍스 해제

        // 철학자가 생각하고 식사하는 코드...
    }
    return NULL;
}

이렇게 하면 *philo->dead 값이 일관되게 유지되고, 여러 스레드가 동시에 변경하면서 발생할 수 있는 오류를 방지할 수 있습니다.


🔹 결론

뮤텍스를 사용하여 *philo->dead에 대한 접근을 보호하는 이유는 다음과 같습니다.

  • 여러 스레드가 *philo->dead 값을 동시에 읽고 수정하는 경쟁 상태를 방지합니다.
  • 한 번에 한 스레드만 안전하게 값을 읽거나 변경할 수 있도록 보장합니다.
  • 프로그램이 예상치 못한 동작을 하지 않도록 만듭니다.

이처럼 뮤텍스 잠금(pthread_mutex_lock)을 통해 데이터 보호가 이루어지며, 이를 통해 다중 스레드 환경에서도 일관성을 유지할 수 있습니다. 🚀

 

philo->dead_lock과 *philo->dead의 연결고리를 찾기 위해서는 코드의 구조 및 사용 방식을 살펴봐야 합니다. 일반적으로 다음과 같은 요소를 확인하면 뮤텍스가 보호하는 대상이 무엇인지 파악할 수 있습니다.


🔹 1. 구조체 정의 확인

우선, philo 구조체가 어떻게 정의되어 있는지를 확인해야 합니다. philo->dead_lock이 어떤 데이터를 보호하는지 보기 위해, t_philo 구조체를 찾아봅시다.

예를 들어, 다음과 같은 구조체가 있다고 가정해 보겠습니다.

typedef struct s_philo
{
    int id;
    int *dead;                // 모든 철학자가 공유하는 "죽음" 상태 변수
    pthread_mutex_t *dead_lock; // "죽음" 상태 변수를 보호하는 뮤텍스
} t_philo;

여기서 연결고리를 찾을 수 있습니다:

  • *philo->dead : int형 변수로, 철학자들의 생존 상태를 저장합니다.
  • philo->dead_lock : 이 dead 변수에 대한 접근을 동기화하기 위한 뮤텍스입니다.

즉, dead_lock이 dead 변수를 보호하고 있다는 것이 명확해집니다.


🔹 2. 뮤텍스 초기화 위치 확인

뮤텍스가 초기화되는 위치를 보면, 이 뮤텍스가 특정 변수(*philo->dead)를 보호하는 용도인지, 아니면 다른 용도인지 알 수 있습니다.

예제:

int main()
{
    pthread_mutex_t dead_lock;
    int dead = 0; // 초기 상태: 아무도 죽지 않음

    pthread_mutex_init(&dead_lock, NULL);

    t_philo philo;
    philo.dead = &dead;
    philo.dead_lock = &dead_lock;

    // 스레드 생성 및 실행 ...
}

이렇게 되어 있다면, dead_lock은 dead 변수를 보호하기 위한 것으로 보입니다.


🔹 3. 뮤텍스 사용 패턴 확인

뮤텍스가 보호하는 대상이 무엇인지는 뮤텍스를 사용하는 코드 패턴을 보면 더욱 확실히 알 수 있습니다.

다음 두 가지 경우를 비교해 보겠습니다.

Case 1: dead_lock이 dead 변수를 보호하는 경우 (일반적인 철학자 문제 코드)

void *philosopher(void *arg)
{
    t_philo *philo = (t_philo *)arg;

    while (1)
    {
        pthread_mutex_lock(philo->dead_lock); // 🔒 "죽음" 상태 보호 시작
        if (*philo->dead == 1)
        {
            pthread_mutex_unlock(philo->dead_lock); // 🔓 보호 해제
            printf("Philosopher %d stops\n", philo->id);
            break;
        }
        pthread_mutex_unlock(philo->dead_lock); // 🔓 보호 해제

        // 철학자가 생각하고 식사하는 코드...
    }
    return NULL;
}
  • pthread_mutex_lock(philo->dead_lock);을 한 뒤 *philo->dead 값을 확인함.
  • pthread_mutex_unlock(philo->dead_lock);을 하기 전까지는 다른 스레드가 dead 값을 변경할 수 없음.
  • 즉, dead_lock이 dead 변수를 보호하는 역할을 함.

Case 2: dead_lock이 다른 용도로 사용되는 경우

void *philosopher(void *arg)
{
    t_philo *philo = (t_philo *)arg;

    while (1)
    {
        pthread_mutex_lock(philo->dead_lock); // 🔒 하지만 보호하는 변수가 다름
        // 철학자가 포크를 집는 코드
        pthread_mutex_unlock(philo->dead_lock); // 🔓 해제

        pthread_mutex_lock(philo->dead_lock); // 🔒 다시 잠금
        // 철학자가 포크를 내려놓는 코드
        pthread_mutex_unlock(philo->dead_lock); // 🔓 해제
    }
    return NULL;
}

이 경우:

  • dead_lock이 dead 변수를 보호하지 않고, 포크(철학자의 식사 과정) 관련 로직을 보호하는 용도로 사용될 가능성이 있음.
  • 즉, dead_lock이 반드시 dead 변수를 보호한다고 단정할 수 없음.

🔹 4. 코드에서 pthread_mutex_lock(philo->dead_lock); 이후에 어떤 변수가 사용되는지 확인

보통, 뮤텍스를 잠근 직후 접근하는 변수가 보호 대상입니다.

pthread_mutex_lock(philo->dead_lock); // 🔒 잠금
if (*philo->dead == 1)  // ✅ 잠근 직후 접근하는 변수: *philo->dead
{
    pthread_mutex_unlock(philo->dead_lock); // 🔓 해제
    return 1;
}
pthread_mutex_unlock(philo->dead_lock); // 🔓 해제

여기서 pthread_mutex_lock()과 pthread_mutex_unlock() 사이에 *philo->dead에 접근하고 있으므로, dead_lock이 dead를 보호한다고 볼 수 있습니다.


🔹 결론: 뮤텍스가 보호하는 변수 찾는 방법

  1. 구조체 정의 확인
    • pthread_mutex_t *dead_lock;과 int *dead;가 같은 구조체 안에 있다면, 보호하는 대상일 가능성이 높음.
  2. 뮤텍스 초기화 위치 확인
    • pthread_mutex_init(&dead_lock, NULL);이 dead와 함께 초기화되는지 확인.
  3. 뮤텍스 사용 패턴 확인
    • pthread_mutex_lock() 직후 접근하는 변수가 보호 대상일 가능성이 큼.
  4. 뮤텍스의 다른 사용처 확인
    • 뮤텍스가 다른 데이터 보호용인지 확인하려면, 코드 전체를 살펴보아야 함.
    • 예를 들어, 같은 dead_lock이 다른 곳에서 포크(fork) 동기화에 사용된다면, dead가 아닌 다른 데이터를 보호하는 용도로 사용될 가능성이 있음.

이 방법을 따라가면, philo->dead_lock이 *philo->dead를 보호하는지, 아니면 다른 용도로 사용되는지 확실하게 알 수 있습니다. 🚀

 

 


🔹 1. 구조체 내부만으로는 보호 대상이 확정되지 않음

pthread_mutex_t *dead_lock;이 int *dead;와 함께 존재한다고 해서 반드시 dead_lock이 dead를 보호하는 것은 아닙니다.

예를 들어, num_times_to_eat도 중요한 공유 데이터일 수 있기 때문에, dead_lock이 이를 보호할 가능성도 있습니다.

즉, 뮤텍스와 보호 대상 변수의 관계는 구조체만 보고 확정할 수 없으며, 실제 코드에서 어떻게 사용되는지를 확인해야 합니다.


🔹 2. pthread_mutex_lock(dead_lock);이 사용된 코드 확인

가장 확실한 방법은 코드에서 dead_lock이 잠금되는 곳을 확인하는 것입니다.

예를 들어, dead_lock을 잠근 후 dead 변수를 읽거나 수정하는 코드가 있다면, dead_lock이 dead를 보호한다고 볼 수 있습니다.

Case 1: dead_lock이 dead를 보호하는 경우

void check_death(t_philo *philo)
{
    pthread_mutex_lock(philo->dead_lock); // 🔒 보호 시작
    if (*philo->dead == 1)
    {
        pthread_mutex_unlock(philo->dead_lock); // 🔓 보호 해제
        return;
    }
    pthread_mutex_unlock(philo->dead_lock); // 🔓 보호 해제
}

이 경우:

  • pthread_mutex_lock(philo->dead_lock); 후에 *philo->dead 값을 확인.
  • 즉, dead_lock이 dead를 보호하고 있음을 알 수 있음.

Case 2: dead_lock이 num_times_to_eat을 보호하는 경우

void update_meal_count(t_philo *philo)
{
    pthread_mutex_lock(philo->dead_lock); // 🔒 보호 시작
    philo->num_times_to_eat -= 1; // ✅ `num_times_to_eat` 값을 변경
    pthread_mutex_unlock(philo->dead_lock); // 🔓 보호 해제
}

이 경우:

  • pthread_mutex_lock(philo->dead_lock); 후에 num_times_to_eat을 변경하고 있음.
  • 즉, dead_lock이 num_times_to_eat을 보호하는 역할을 할 수도 있음.

🔹 3. dead_lock이 dead를 보호하는지 확실히 판별하는 방법

  1. 구조체의 변수 위치만으로는 확정할 수 없음.
  2. dead_lock을 사용하는 코드들을 살펴본다.
    • dead_lock이 잠긴 후 dead를 접근하는 코드가 많다면, dead_lock이 dead를 보호하는 것.
    • dead_lock이 잠긴 후 num_times_to_eat을 접근하는 코드가 많다면, num_times_to_eat을 보호하는 것.
  3. 혼용되는 경우가 있는지 체크한다.
    • 하나의 뮤텍스가 여러 개의 변수를 보호할 수도 있으므로, dead_lock이 두 변수(dead, num_times_to_eat)를 동시에 보호할 수도 있음.
    • 이를 방지하려면 meal_lock 같은 별도의 뮤텍스를 사용해야 함.

🔹 4. 개선된 설계: 명확한 보호 관계 설정

🚀 각 변수에 대해 전용 뮤텍스를 사용할 경우

pthread_mutex_t *dead_lock;  // 🔒 `dead` 보호용
pthread_mutex_t *meal_lock;  // 🔒 `num_times_to_eat` 보호용

이렇게 설계하면 dead_lock이 dead를 보호하는 것이 확실해지고, meal_lock이 num_times_to_eat을 보호하는 것이 명확해집니다.


🔹 결론

🔎 구조체만으로는 dead_lock이 dead를 보호한다고 확정할 수 없음.
🔎 실제 코드에서 pthread_mutex_lock(dead_lock);이 호출된 후 어떤 변수를 다루는지 분석해야 함.
🔎 보다 명확한 설계를 위해 meal_lock과 같은 별도의 뮤텍스를 사용하는 것이 좋음. 🚀

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

pthread_mutex_init NULL 초기화  (0) 2025.03.13
뮤텍스 포인터 차이  (0) 2025.03.13
pthread_mutex_lock pthread_mutex_unlock 설명  (0) 2025.03.12
pthread_create pthread_join 설명  (0) 2025.03.12
pthread_mutex_lock unlock  (0) 2025.03.10