[philosophers] 구현

J_JEON·2022년 9월 7일
1

Philosophers

목록 보기
4/5

구현순서

  1. argv 확인
  2. 파싱 및 초기화
  3. 스레드 생성
    먹기
    잠자기
    생각하기
  4. 모니터링하기
  5. 리소스 회수하고 종료하기

Main

#include "philosophers.h"

int	main(int argc, char *argv[])
{
	t_param	param;

	if (argc != 5 && argc != 6) 
    // 받아온 argv가 5개 또는 6개여야함
		return (ft_error("[Error]check num of arg"));
        // 아니라면 에러출력 후 종료
	if (ft_philo_init(argc, argv, &param))
    // 파싱 및 초기화
		return (1);
	if (ft_start_philo(&param, (&param)->philo))
    // 스레드 생성 및 모니터링
		return (1);
	return (0);
}

argv확인

int	check_is_num(char *str) // 모든 값이 숫자만 있는지 확인
{
	int	i;

	i = 0;
	while (str[i] != '\0')
	{
		if (str[i] < '0' || str[i] > '9')
			return (1);
		i++;
	}
	return (0);
}

int	check_argv(int argc, char *argv[])
{
	int	i;

	i = 1;
	while (i != argc)
	{
		if (check_is_num(argv[i]))
        // 숫자만 들어왔는지 확인
			return (1);
		i++;
	}
	if (ft_atoi(argv[1]) < 1)
    // 철학자는 1명 이상이어야함
		return (1);
	if (ft_atoi(argv[2]) < 0)
    // 생존시간은 0초 이상이어야함
		return (1);
	if (ft_atoi(argv[3]) < 0)
    // 밥먹는 시간은 0초 이상이어야함
		return (1);
	if (ft_atoi(argv[4]) < 0)
    // 수면 시간은 0초 이상이어야함
		return (1);
	if (argc == 6) 
    // 최대 식사횟수를 받았다면
		if (ft_atoi(argv[5]) < 0)
        // 최대 식사횟수는 0번 이상이어야함
			return (1);
	return (0);
}

파싱 및 초기화

int	ft_philo_init(int argc, char *argv[], t_param *par)
// 실행에 필요한 여러 값들을 저장하고 초기화
{
	int	i;

	i = 0;
	if (check_argv(argc, argv)) // 유효한 값들인지 확인
		return (ft_error("[Error]check argv data"));
	par->philo_num = ft_atoi(argv[1]); // 필요한 값들 저장
	par->time_to_die = ft_atoi(argv[2]);
	par->time_to_eat = ft_atoi(argv[3]);
	par->time_to_sleep = ft_atoi(argv[4]);
	if (argc == 6)
		par->must_eat_num = ft_atoi(argv[5]);
	else
		par->must_eat_num = -1;
    if (par->must_eat_num == 0)
    	return (ft_error("0ms	all philo eat 0 time));
	par->fork_st = malloc(sizeof(int) * par->philo_num);
    // 포크사용여부를 확인할 fork_st 저장공간 확보
	if (par->fork_st == NULL)
		return (ft_error("[Error]malloc fork fail"));
	while (i < par->philo_num)
    // 포크의 사용여부를 사용하고있지않은 상태인 0으로 설정
		par->fork_st[i++] = 0;
	par->is_all_safe = 1;
    // 생존여부를 알수있는 is_all_safe를 생존상태인 1로 설정
	if (ft_make_philo(par))
    // 각 철학자들에게 필요한 리소스 설정 및 확보
		return (ft_error("[Error]malloc philo fail"));
	if (ft_make_forks(par))
    // 포크 및 기타 mutex 리소스 설정 및 확보
		return (1);
	return (0);
}

int	ft_make_philo(t_param *par)
// 각 철학자들에게 필요한 리소스 확보 및 설정
{
	int		i;

	i = 0;
	par->philo = malloc(sizeof(t_param) * (par->philo_num));
    // 각 철학자들의 자료를 저장할 t_philo 구조체 저장공간 확보
	if (par->philo == NULL)
		return (1);
	while (i < par->philo_num)
    // 각 철학자들의 값 초기화
	{
		par->philo[i].eat_count = 0;
        // 식사횟수
		par->philo[i].last_eat_time = 0;
        // 마지막 식사시간
		par->philo[i].leftfork = i;
        // 왼쪽 포크 번호
		par->philo[i].rightfork = i + 1;
        // 오른쪽 포크 번호
		par->philo[i].param = par;
        // 공유자원
		par->philo[i].philo_id = i + 1;
        // 철학자 번호
		if (i != 0 && i + 1 == par->philo_num)
			par->philo[i].rightfork = 0;
        // 만약 마지막 철학자라면 왼쪽 포크가 0번째 포크가 됨
		i++;
	}
	return (0);
}

int	ft_make_forks(t_param *param)
// 포크 및 기타 mutex 리소스 확보 및 설정
{
	int	i;

	i = 0;
	if (pthread_mutex_init(&(param->print), NULL))
		return (ft_error("[Error]print mutex init fail"));
    // 한번에 하나씩 출력하도록 lock해줄 print mutex 초기화
	if (pthread_mutex_init(&(param->search_fork), NULL))
		return (ft_error("[Error]search_fork mutex init fail"));
    // 하나의 프로세스만 포크사용여부를 확인하도록 search_fork mutex 초기화
	if (pthread_mutex_init(&(param->eat), NULL))
		return (ft_error("[Error]eat mutex init fail"));
    // 하나의 프로세스만 식사여부를 확인하도록 eat mutex 초기화
	param->forks = malloc(sizeof(pthread_mutex_t) * (param->philo_num));
    // 포크가 될 forks mutex 저장공간 확보
	if (param->forks == NULL)
		return (ft_error("[Error]malloc forks fail"));
	while (i < param->philo_num)
	{
		if (pthread_mutex_init(&param->forks[i], NULL))
			return (ft_error("[Error]forks mutex init fail"));
      	// 각 포크가될 forks mutex 초기화
		i++;
	}
	return (0);
}

스레드 생성

int	ft_start_philo(t_param *par, t_philo *philo)
// 스레드들을 생성하고 모니터링 시작
{
	int		i;
	void	*philo_v;

	i = 0;
	par->start_time = ft_get_time();
    // 시작시간 설정
	while (i < par->philo_num)
	{
		philo_v = (void *)&(philo[i]);
        // 스레드에 넘겨줄 철학자 정보를 void*로 형변환
		if (pthread_create(&(philo[i].tid), NULL, do_philo, philo_v) == -1)
			return (ft_error("[Error]thread create fail"));
        // 철학자 하나당 스레드를 1개씩 생성 및 실행
		i++;
	}
	ft_wait(par, par->time_to_eat);
    // 첫 식사가 끝날 수 있도록 모니터링 대기
	ft_check_die(par);
    // 철학자가 모두 살이있는지, 정해진 만큼 식사했는지 모니터링
	return (0);
}

스레드 내부

void	*do_philo(void *philo)
// 철학자들의 기본 행동
{
	t_philo	*phil;
	t_param	*param;

	phil = (t_philo *)philo;
	param = phil->param;
	if (phil->philo_id % 2 != 1)
		ft_wait(param, 1);
    // 붙어있는 철학자들이 동시에 포크를 집는것을 방지하기 위해
    // 짝수번째 철학자들은 1ms만큼 대기 후 시작
	while (param->is_all_safe)
    // 모두가 생존해있는 동안
	{
		while (get_fork1(phil, param))
        // 포크를 집고 식사
			usleep(100);
            // 포크를 못집었다면 0.1ms 대기 후 재확인
		do_sleep(phil, param);
        // 잠자기
		do_think(phil, param);
        // 생각하기
		usleep(100);
        // 일어나자마자 바로 포크를 집지않도록 대기
	}
	return (0);
}

먹기

int	get_fork1(t_philo *p, t_param *param)
// 포크를 집기
{
	if (param->is_all_safe == 0)
    // 만약 누군가가 죽은상태면 바로 종료
		return (0);
	pthread_mutex_lock(&param->search_fork);
    // 포크 사용여부를 확인할때에 누군가 접근하지 못하도록 lock
	if (param->fork_st[p->leftfork] == 0 && param->fork_st[p->rightfork] == 0)
    // 양쪽 포크 둘다 사용가능이라면 식사시작, 아니라면 종료
	{
		pthread_mutex_lock(&param->forks[p->leftfork]);
        // 왼쪽포크 점유
		ft_print(param, p, "has taken a fork");
        // 포크 집었다고 출력
		param->fork_st[p->leftfork] = 1;
        // 왼쪽포크 점유 표시
		pthread_mutex_lock(&param->forks[p->rightfork]);
        // 오른쪽 포크 점유
		ft_print(param, p, "has taken a fork");
		param->fork_st[p->rightfork] = 1;
        // 오른쪽 포크 점유 표시
		do_eat(p, param);
        // 식사 시작
		pthread_mutex_unlock(&param->search_fork);
        // 포크 사용여부를 다른 프로세스가 접근할 수 있도록 unlock
		ft_wait(param, param->time_to_eat);
        // 식사시간만큼 대기
		param->fork_st[p->leftfork] = 0; // 포크 점유 해제
		param->fork_st[p->rightfork] = 0;// 포크 점유 해제
		pthread_mutex_unlock(&param->forks[p->leftfork]);
        // 포크 점유 해제
		pthread_mutex_unlock(&param->forks[p->rightfork]);
        // 포크 점유 해제
		p->eat_count++; // 식사횟수 증가
		return (0);
	}
	pthread_mutex_unlock(&param->search_fork);
    // 포크 사용여부 확인 unlock
	return (1);
}

void	do_eat(t_philo *philo, t_param *param)
// 식사 시작
{
	long long	time;

	if (param->is_all_safe == 0)
    // 누군가 죽은 상태라면 바로 종료
		time = 0;
	else
	{
		pthread_mutex_lock(&(param->eat));
        // 식사시작동안 다른 프로세스가 식사시간을 변경하지 못하도록 lock
		time = ft_get_time() - param->start_time;
        // 현재의 ms 계산
		philo->last_eat_time = time;
        // 마지막 식사시간을 현재 시간으로 수정
		ft_print(param, philo, "is eating");
        // 식사시작을 출력
		pthread_mutex_unlock(&(param->eat));
        // unlock
	}
}

잠자기

void	do_sleep(t_philo *philo, t_param *param)
{
	if (param->is_all_safe == 0)
    // 누군가 죽은 상태라면 잠을 자지않고 종료
		param->is_all_safe = 0;
	else
	{
		ft_print(param, philo, "is sleeping");
        // 잠자기 시작을 출력
		ft_wait(param, param->time_to_sleep);
        // 잠자는 시간만큼 대기
	}
}

생각하기

void	do_think(t_philo *philo, t_param *param)
{
	if (param->is_all_safe == 0)
    // 누군가 죽었다면 생각하지않고 바로 종료
		param->is_all_safe = 0;
	else
	{
		ft_print(param, philo, "is thinking");
        // 생각하기 시작을 출력
	}
}

모니터링하기

void	ft_check_die(t_param *par)
{
	while (par->is_all_safe)
    // 모두 살아있는동안만 반복
	{
		pthread_mutex_lock(&(par->eat));
        // 식사시간을 정확히 확인하기위해 lock
		if (check_eat_time(par)) // 식사시간 확인
			break ; // 누군가 죽었다면 반복 종료
		if (par->must_eat_num != -1) // 식사횟수가 정해져있다면
			if (check_eat_num(par)) // 식사횟수 확인
				break ; // 모두 다 정해진만큼 먹었으니 반복 종료
		pthread_mutex_unlock(&(par->eat));
        // 식사시간 확인이 끝났으니 unlock
		usleep(100);
	}
	pthread_mutex_unlock(&(par->eat));
    // 식사시간 확인이 끝났으니 unlock
	finish_thread(par);
    // 누군가가 죽었거나 식사횟수를 다 채우면 리소스 회수 및 종료
}

식사시간 확인

int	check_eat_time(t_param *p)
{
	long long	time;
	int			i;

	i = 0;
	while (i < p->philo_num)
    // 각 철학자들을 순회
	{
		time = ft_get_time() - p->start_time; // 현재시간
		if ((time - p->philo[i].last_eat_time) > p->time_to_die)
        // 현재시간과 마지막 식사시간이 생존시간 초과만큼 차이가 난다면
		{
			pthread_mutex_lock(&p->print);
            // 한번에 하나만 출력하도록 출력 lock
			p->is_all_safe = 0;
            // 모두 살아있지않은 상태인 0으로 생존여부 변경
			printf("%lldms	%d	is died\n", time, p->philo[i].philo_id);
            // 죽은 시간과 죽은 철학자번호를 출력
			pthread_mutex_unlock(&p->print);
            // 출력 unlock
			return (1);
		}
		i++;
	}
	return (0);
}

식사횟수 확인

int	check_eat_num(t_param *p)
{
	long long	time;
	int			i;
	int			check;

	i = 0;
	check = 0;
	while (i < p->philo_num)
    // 철학자들을 순회
	{
		if (p->philo[i].eat_count >= p->must_eat_num && p->must_eat_num != -1)
        // 철학자가 식사횟수를 충족했다면
			check++; // 카운트 증가
		i++;
	}
	if (check == p->philo_num)
    // 모든 철학자가 식사횟수를 충족했다면
	{
		pthread_mutex_lock(&p->print);
        // 출력 lock
		p->is_all_safe = 0;
        // 종료해야하므로 누군가 죽었다고 가정시킴
		time = ft_get_time() - p->start_time;
		printf("%lldms	all philo eat %d time\n", time, p->must_eat_num);
        // 현재 시간과 식사를 모두 끝냈다고 출력
		pthread_mutex_unlock(&p->print);
        // 출력 unlock
		return (1);
	}
	return (0);
}

리소스 회수하고 종료하기

void	finish_thread(t_param *param)
{
	int	i;

	i = 0;
	while (i < param->philo_num)
    // 스레드들을 모두 종료
	{
		pthread_join(param->philo[i].tid, NULL);
        // 스레드가 끝날때까지 대기하다가 리소스를 회수하는 pthread_join사용
		i++;
	}
	i = 0;
	while (i < param->philo_num)
    // mutex들의 리소스 회수
	{
		pthread_mutex_destroy(&(param->forks[i]));
		i++;
	}
	free(param->forks); // malloc한 저장공간들 free
	free(param->philo);
	free(param->fork_st);
	pthread_mutex_destroy(&(param->print)); // mutex 리소스 회수
	pthread_mutex_destroy(&(param->eat));
	pthread_mutex_destroy(&(param->search_fork));
}
profile
늅늅

8개의 댓글

comment-user-thumbnail
2023년 9월 6일

안녕하세요, 질문이 있는데 혹시 해도 될까요?

1개의 답글