42seoul:: Philosophers 문제

jahlee·2023년 5월 25일
0

개인 공부

목록 보기
11/23
post-thumbnail

Subject 링크

허용함수정리 링크

컴파일 옵션

-Wall -Wextra -Werror -pthread
data race를 보고싶다면 -fsanitize=thread -g를 추가

Thread 란?

프로세스 내부에 있는 CPU 수행 단위를 의미한다.

프로세스는 운영체제로 부터 할당받는 작업의 단위이고, 쓰레드는 프로세스가 할당받은 자원을 이용하는 실행의 단위이며 프로세스 내에 여러개가 생길 수 있다.

Mutex 란 ?

Mutual Exclusion 의 약자로 상호배제라고 한다. 특정 쓰레드 단독으로 들어가야되는 코드 구역에서 동기화를 위해 사용되는 동기화 기법입니다.

이해하기쉽게 예시를 들어보자면 공통으로 사용하는 화장실에 대해 화장실 열쇠를 뮤텍스라 볼수있다.
한번에 한명만 화장실을 사용해야하기 때문에 키를 가지고 있지 않은 사람들은 키를 받을때 까지 기다렸다가 화장실을 이용한다고 생각하면 된다.

문제에 대한 이해

n명 만큼의 철학자가 존재하고 n개 만큼의 포크가 있으면 철학자들은 둥근 책상에 원형으로 앉아있다.
철학자는 먹고 자고 생각하는 행동을 한다.
각 철학자들의 양쪽에는 포크가 하나씩 존재하며 철학자가 식사를 하려면 양손에 포크를 들고있어야만 식사가 가능하다.
조건에 따라 철학자의 상태를 정의해주면 된다.

예시로 철학자 4명이 410 ms 동안 식사를 안하면 죽고 밥먹는 시간과 자는 시간이 200 ms라면 철학자들은 죽으면 안되고 계속 생존해 있어야 한다. 하지만 390 ms 동안 식사를 안하면 죽고 이외의 조건이 동일하다면 철학자가 죽고 프로세스가 종료되어야한다.

한명일 때는 어느 조건에서든 양손에 포크를 들지 못하기 때문에 죽는다.

dead lock, data race를 주의하자!

예시 코드

먼저 꼭 필요한 뮤텍스들만 사용하고 락을 거는 부분에 대해서 잘생각하며 코드를 짜야 시간이 덜 밀려 철학자가 생존할 수 있게되므로 유의하자!

주어진 조건에 맞는지 판별을 해주고 공통자원인 info와 각 필로들을 초기화 시켜준다음 쓰레드를 활성화하면 된다.

int	main(int argc, char **argv)
{
	t_info	info;
	t_philo	*philo;

	if (argc != 5 && argc != 6)
		return (ph_error("usage: num_of_philosophers time_to_die time_to_eat" \
		" time_to_sleep [num_of_times_to_must_eat]", NULL, NULL));
	if (init_info(&info, argc, argv))
		return (ph_error("init_info error: wrogn argv or malloc error", \
		&info, NULL));
	if (init_info_mutex(&info))
		return (ph_error("init_info_mutex error: pthread_mutex_init error", \
		&info, NULL));
	if (init_philo(&philo, &info))
		return (ph_error("init_philo error: malloc error", &info, NULL));
	if (work_philo(&info, philo))
	{
		unlock_destroy_all(philo);
		return (ph_error("work_philo: thread create error", &info, philo));
	}
}

모든 스레드를 동시에 시작하기위해 메인 스레드에서 start 뮤텍스를 활용하고, 모든 스레드가 생성되었을때 각 스레드가 동작하도록 하였다.

void	*thread_action(void *ptr)
{
	t_philo	*philo;

	philo = ptr;
	pthread_mutex_lock(&philo->info->start_mutex);
	if (is_finished(philo->info))
	{//스레드를 만드는중 에러가 발생했을때 이미 만들어진 스레드들에 대한 예외 처리이다.
		pthread_mutex_unlock(&philo->info->start_mutex);
		return (NULL);
	}
	philo->last_meal_time = get_current_time();
	pthread_mutex_unlock(&philo->info->start_mutex);
	if (philo->id % 2 == 0)
		pass_time(philo->info->time_to_eat / 2, philo);
	while (42)
	{
		if (check_fork(philo, philo->info, philo->left) || \
		check_fork(philo, philo->info, philo->right) || \
		start_eating(philo, philo->info) || \
		philo_print("is sleeping", philo, philo->info) || \
		pass_time(philo->info->time_to_sleep, philo) || \
		philo_print("is thinking", philo, philo->info))
			break ;
	}
	return (NULL);
}

usleep 같은 경우 정확히 주어진 초가 지난다음 동작한다는 보장이 없기 때문에 적당히 적은 값으로 쪼개어 확인하며 시간을 보내준다.
하지만 usleep은 시스템 콜이기때문에 너무 많이 호출해도 좋지않기때문에 적당한 값을 잘 선택하자.

int	pass_time(long long wait_time, t_philo *philo)
{
	long long	start;
	long long	current_time;

	start = get_current_time();
	while (42)
	{
		current_time = get_current_time();
		if (current_time - start >= wait_time)
			return (0);
		if (is_dead_philo(philo, philo->info))
			return (1);
		usleep(500);
	}
}

참고 링크

[철학자들은 왜 굶어 죽었을까?] 유투브 영상 링크

뮤텍스와 세마포어 설명 블로그 링크

팔만코딩경 philo설명 링크

0개의 댓글