본문 바로가기
42/42cursus

[philosophers] 2. 뮤텍스(mutex)

by jaemjung 2022. 2. 19.

과연 철학자들의 무분별한 포크 위조를 어떻게 막을 것인가?

그 해답은 바로 mutex에 있었다.

 

mutex란 MUTual EXclusion의 약자로, 즉 어떤 코드의 실행을 특정한 스레드만 가능하도록 일시적으로 제한하는 동기화 기법이다. 

 

뭔?소린지 이해가 안가니 코드를 통해 살펴보자

 

int	philo_eat(t_philosopher *philo)
{	
	philo_take_fork(philo);
	philo_print(philo, EAT);
	philo->time_fed = get_time();
	alt_sleep(philo->info->philo_args[T_EAT]);
	return (0);
}

 

철학자가 식사를 하는 부분의 코드이다. 각 스레드에서는 포크라는 bool 변수의 값이 true인지 확인하고, 먹은 시간을 현재 시간으로 갱신하고, 인자로 들어온 식사 시간만큼 usleep을 하여 시간을 지연한다.

 

이때 중요한 점은 먹는 시간을 갱신하는 코드는 상호 배제적으로 실행되어야 하는 코드라는 점이다. 반드시 포크 두 개를 들어야 식사를 할 수 있으니, 해당 코드를 동시에 실행할 수 있는 스레드는 포크를 2개 든 스레드로 제한되어야 하는 것이다.

 

이처럼 특정 코드를 실행하는 스레드가 제한되는 경우, mutex를 사용하여 해당 코드의 실행 가능 여부를 결정해줄 수 있다.

 

이러한 뮤텍스는 pthread_mutex_init이라는 함수로 생성할 수 있다.

 

int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
			const pthread_mutexattr_t *restrict attr);

 

첫 번째 인자로는 생성되는 뮤텍스가 저장될 주소값, 두 번째 인자로는 해당 뮤텍스의 attribute가 들어가는데, NULL로 지정하면 기본으로 설정된다. 

 

이렇게 생성한 mutex는 lock과 unlock 함수를 사용해 특정 코드 블록에 접근하는 스레드를 제한해줄 수 있다.

 

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

자 이제 boolean 값이었던 포크를 mutex로 대체하면 어떻게 될까?

 

int	philo_eat(t_philosopher *philo)
{	
	// 왼쪽 포크와 오른쪽 포크를 모두 잡아야
	pthread_mutex_lock(philo->fork_l);
	philo_print(philo, FORK_L);
	pthread_mutex_lock(philo->fork_r);
	philo_print(philo, FORK_R);
    	// 이 아래쪽의 코드를 실행할 수 있다.
	philo_print(philo, EAT);
	philo->time_fed = get_time();
	alt_sleep(philo->info->philo_args[T_EAT]);
	// 임계구역의 실행이 종료된 후에는 반드시 unlock을 해주어야 다른 스레드가 해당 코드를 실행할 수 있다.
   	pthread_mutex_unlock(philo->fork_r);
	pthread_mutex_unlock(philo->fork_l);
	return (0);
}

 

이렇게 한 스레드에서 mutex를 lock하게 되면,

다른 스레드는 해당 코드를 실행하기 위해 mutex의 unlock을 기다리게 된다.

-> 따라서 한 번에 한 스레드만 특정 코드를 실행하는 것을 보장할 수 있게 되는 것이다.

 

본 과제에서는 식사를 하기 위해 2개의 포크가 필요하니, 2개의 뮤텍스가 lock이 되었을 때 식사를 하도록 하면?

-> 포크를 잡은 철학자 스레드만이 식사 시간을 갱신할 수 있도록 보장할 수 있는 것이다.

 

이렇게 특정한 조건을 만족한 스레드만이 진입해야하는 코드 구역을 임계구역(Critical section)이라고 한다. 위의 식사하는 코드에서는 식사를 시작한다는 로그를 출력하고, 마지막 식사 시간을 갱신하고, 식사 시간만큼 usleep을 해주는 부분까지가 임계구역이 된다. 

 

같은 원리로 로그를 print하는 것 역시 한번에 한 스레드만 하도록 mutex를 걸어준다면? -> 출력되는 로그가 엉키는 것을 막아 줄 수 있다!


헌데 이렇게 상호 배제가 되도록 코드를 바꿔주었더니, 무시무시한 일이 일어났으니... 바로 모든 철학자가 하나의 포크만을 잡고 식사를 하지 못하게 되는 대참사, 이른바 교착상태가 일어나게 된 것이다...

 

댓글