본문 바로가기

42cursus

philosophers[철학자] - 룰 그리고 쓰레드, 뮤텍스, 세마포어, 프로세스

0. 프로젝트 목표

쓰레드와 뮤텍스를 사용하여 공유 메모리에 대한 처리 하기

쓰레드와 세마포어를 사용하여 공유 메모리에 대한 처리 하기

프로세스와 세마포어를 사용하여 프로세스간 동기화에 대한 처리 하기

 

 

 

1. 기본 룰

dining philosophers problem : https://selvarajahkesavan.medium.com/the-dining-philosophers-problem-de586df365bc

1) 철학자들이 둥근 테이블에 앉아 있음

2) 3가지 상태가 존재함 (eat, sleep, think : 동시상태는 없음)

3) 중앙에 스파게티 접시가 있고, 철학자 수만큼의 포크가 존재

4) 한 철학자당 포크 2개로 식사를 함

5) 각 철학자들은 서로 말도 못하고, 상태확인도 하지 못함 (포크만 보는 것)

6) 철학자는 먹고 나서 포크를 두고 잠 (eating -> sleeping)

7) 철학자는 자고 나서 생각함 (sleeping -> thinking)

8) 이 시뮬레이션은 한명의 철학자가 죽으면 끝남

9) 혹은 인자로 [모든 철학자가 꼭 먹어야하는 식사 수]를 만족하면 시뮬레이션은 멈춤

10) 인자는 다음과 같음

 ex) 철학자수 수명 먹는시간 자는시간 [모든 철학자의 식사 수]

 ex)   5 800 200 200 [7]

11) 각 철학자는 고유번호를 1부터 받음

12) N - 1, N, N + 1 순으로 앉아 있음

13) 상태변화시에 출력이 되어야하고, 출력이 섞이면 안됨

 ex) 시간 철학자고유번호 상태

 ex) 800 1 is eating

14) 죽은 상태와 죽었다는 프린트가 10ms을 초과할 수 없음

 

-> 과제의 목표는 쓰레드, 뮤텍스, 세마포어, 프로세스를 활용하여

-> 언제 철학자들이 상태변화를 하고, 철학자가 죽었는지 안죽었는지 파악하며 시뮬레이션 함

 

 

 

2. 사용하는 함수들

시간함수, 쓰레드함수, 뮤텍스함수, 세마포어함수가 존재

malbongcode.tistory.com/160

 

philosophers - 외부함수 정리하기

=== 시간 함수 === int usleep(useconds_t microseconds) 마이크로초 만큼 쓰레드 대기 헤더  - unistd.h 파라미터  - microseconds: 1 / 1000 * 1000 초 반환  - 성공 시: 0 반환  - 실패 시: -1 반환 1 2 3..

malbongcode.tistory.com

 

3. philo_one VS philo_two VS philo_three

philo_one

1) 각 철학자는 쓰레드로 구성

2) 포크는 양쪽 옆에 있음

2) 포크는 뮤텍스로 보호함

-> 뮤텍스로 쓰레드가 접근하는 것을 제어할 수 있냐?

 

philo_two

1) 각 철학자는 쓰레드로 구성

2) 포크는 테이블 중간에 있음

3) 포크는 세마포어로 보호함

-> 세마포어로 쓰레드가 접근하는 것을 제어할 수 있냐?

 

philo_three

1) 각 철학자는 프로세스로 구성

2) 포크는 테이블 중간에 있음

3) 포크는 세마포어로 보호함

-> 프로세스와 세마포어로 동기화 할 수 있냐?

 

 

 

4. 쓰레드와 뮤텍스

쓰레드

출처: https://developerhenrycho.tistory.com/17

 - 스레드는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다 (위키)

 - 예시) 입력만 받는 쓰레드, 출력만하는 쓰레드

 - 처리량을 올릴 수 있음

 - 동시에 여러개의 쓰레드를 만들어서 여러가지일을 한번에 처리할 수도 있음

 - 허나, 쓰레드가 하나의 메모리를 동시에 접근한다면?

  -> A쓰레드는 전역변수a를 10으로 수정하는데

  -> 그와 동시에 B쓰레드가 전역변수a를 0으로 수정한다면?

  -> 공유메모리 문제 -> 그것을 뮤텍스로 보호

 

 

뮤텍스

뮤텍스 출처: https://frontjang.info

 - 상호 배제(mutual exclusion, Mutex, 뮤텍스)는 동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용되는 것 (위키)

 - 예시) 전역변수a에 해당하는 뮤텍스를 A쓰레드가 가지고 있다면 수정하고, B쓰레드는 전역변수a에 대한 뮤텍스를 가질 때까지 대기함

 - 나는 권한, 소유권이라고 생각하고 사용함

 - philosophers에서 각각의 포크가 뮤텍스로 보호되고 있으므로, 두 철학자가 하나의 포크를 두고 싸우지 않게 뮤텍스로 보호하는 것

 

 

 

5. 쓰레드와 세마포어

출처: https://sycho-lego.tistory.com/11

세마포어

 - philosophers에서는 named semaphore를 사용함 (이름있는 세마포어), 파일로 관리되는 듯

 - 기본적으로 공유자원에 대한 접근을 제어한다는 면에서 뮤텍스와 동일

 - 세마포어는 뮤텍스와 달리 공유자원에 대한 접근을 "value개 만큼 접근"하게 해줌 (뮤텍스는 하나)

 - 세마포어는 뮤텍스와 달리 쓰레드뿐만아니라 프로세스 동기화에도 영향을 미침

 - 세마포어는 뮤텍스와 달리 자신 가지고 있지 않은 것을 unlock해도 됨

  -> philo_one에서는 포크마다 뮤텍스를 두고, 각 철학자에게 l_fork, r_fork를 두어 왼쪽오른쪽 포크에 대한 뮤텍스 검사를 했다면 (양쪽에  포크가 있어야 하므로)

  -> philo_two에서는 포크 세마포어로 두고 value값을 포크 수만큼으로 설정하여, 각 철학자가 포크를 가져가면 됨 (포크들이 중앙에 있으므로)

 

 

 

6. 프로세스와 세마포어

 - 프로세스는 메모리 자원을 그대로 복제하고 독립하기 때문에 전역변수나 메모리로 동기화flag를 설정할 수 없음

 - 프로세스끼리 통신은 pipe를 주로 이용함

 - 여기서는 심지어 pipe도 이용하지 못함

 - 그래서 세마포어로 통신함

  -> "뮤텍스는 메모리, 세마포어는 파일"로 생각하면 프로세스끼리 세마포어로 통신할 수 있다는 것을 알게 됨

 - philo_three에서는 철학자들을 프로세스로 두고, 자신이 죽거나 [모든 철학자가 꼭 먹어야하는 식사 수]를 마치면

   세마포어를 unlock함 (post) -> 메인 프로세스에서 해당 세마포어를 얻어서 처리해주면 됨

 

 

7. 마무리 - 어려웠던점, 나만의 해결방법(옳은 방법이 아닐 수도)

 - 전반적인 쓰레드, 뮤텍스, 세마포어, 프로세스에 대한 이해가 부족하면 어려운 문제임

 - 또한 "어떻게 상태에 대한 동기화를 할 것인가"에 생각도 매우 중요한 과제

 - 시뮬레이션을 종료해야하는 상황이 발생하면(죽거나 다먹거나)

   -> 종료상태를 출력하고, 더 이상 출력을 막기위해 out에 대한 lock을 풀지 않음

 - 시뮬레이션을 계속 돌려보면, 오차가 발생하여 안죽어야 하는 상황에 죽는 경우를 늦추기 위해서 die체크를 3ms여유분을 둚

 - usleep()의 오차 문제를 해결하기 위해 많은 노력을 했고, 그 중에 가장 좋은 것은 usleep()함수를 나눠서 사용하는 것임

1
2
3
4
5
6
usleep(time * 1000); /* 이런식으로 사용하면 오차 발생 !!! */
 
/* 나눠서 사용하기 */
start = get_cur_time();
while (get_cur_time() - start < time)
    usleep(100);
cs

 

philo_one

 - philo_one에서 어려웠던 점은 애초에 구조의 첫걸음이라서 구조잡기가 가장 힘들었음

 - 또한, 쓰레드에는 인자가 하나가 들어가는데 인자를 어떤식으로 넘겨야하는지에 대한 고민도 많이 함 (전역변수 사용하지 않음)

  -> 왜냐하면, 철학자가 자신의 넘버가 몇인지 알아야 하기 때문 (포크도 마찬가지)

  -> 결국 인자를 담고있는 구조체A와, 뮤텍스정보를 담고있는 구조체B를 만들고

  -> philos구조체 배열을 만들어서 philos[i]에 구조체A와 구조체B를 참조하는 변수를 둚

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct        s_philo
{
   pthread_t         tid;
    int               num;
    int               r_fork;
    int               l_fork;
    int               eat_cnt;
    struct s_rule     *rule;
    struct s_mutex    *mutex;
    struct timeval    start_tv;
    struct timeval    life_tv;
}                     t_philo;
cs

 

philo_two

- philo_two는 philo_one에서 사용한 뮤텍스를 세마포어로 변경해주면 문제를 해결함

  -> 허나, 세마포어에 대한 전반적인 이해가 없다면 작성하면 안됨

- 포크를 세마포어로 두면 philo_one보다 로직이 간단하다라는 것을 알 수 있음

 

philo_three

 - philo_three는 철학자를 프로세스로 사용한다는 점이 골치아픔

  -> 왜냐하면 프로세스끼리 통신은 파이프로 하는 것으로 알고 있기 때문

  -> 허용된 함수엔 pipe가 없고, 메모리는 복제되어 독립해서 존재하기 때문에 사용할 수도 없음

  -> 결국 남아 있는 것은 세마포어임

 - 그럼 어떻게 메인프로세스에서 철학자들의 죽음과, must_eat인자에 대한 캐치를 할 수 있냐면

  -> 자식 프로세스(철학자)에서 자신의 상태를 모니터하는 쓰레드를 만들고

  -> 만약, 그 쓰레드에서 철학자 수명이 다 되었으면 sem_post(die_semaphore)로 함

  -> 메인 프로세스에서는 sem_wait(die_semaphore)로 캐치하면 철학자의 죽음을 캐치할 수 있음

  -> 근데, "must_eat인자가 들어오면 must_eat캐치도 하고 die캐치도 해야한다면 어떻게 하지?" 라는 고민을 하게 됨

  -> 일단, must_eat캐치는 철학자 프로세스의 모니터하는 쓰레드에서 must_eat만큼 먹었다면 sem_post(eat_semaphore)를 함

  -> 메인 프로세스에서는 sem_wait(eat_semaphore)를 철학자 수만큼 세마포어를 잡으면 되는 것

 - 그럼 sem_wait(die_semaphore)도 하고, sem_wait(eat_semaphore)도 하려면 메인 프로세스에서 쓰레드를 만들어야 함

  -> 허나, 만약 각각의 쓰레드를 join해서 종료할 때까지 대기한다면, 죽음을 캐치하더라도 must_eat쓰레드가 아직 wait중이라서 종료가 되지 않는 문제가 있음

  -> 그래서 나는 다음과 같이 해결함

  -> join은 die를 캐치하는 쓰레드만 join함

  -> eat을 캐치하는 쓰레드는, 만약 eat을 철학자 수만큼 캐치했다면, wait_post(die_semaphore)로 풀어줘서

  -> die도 캐치하게 하여 종료시킴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void        *wait_for_die(void *philos)
{
    t_philo    *phs;
 
    phs = (t_philo *)philos;
    sem_wait(phs->sem->die);
    return (philos);
}
 
void        *wait_for_eat(void *philos)
{
    int        i;
    t_philo    *phs;
 
    phs = (t_philo *)philos;
    i = 0;
    while (i < phs->rule->ph_num)
    {
        sem_wait(phs->sem->eat);
        i++;
    }
    sem_wait(phs->sem->out);
    printf("%ld\t\tall philos ate it!!!\n", get_time(phs->start_tv));
    sem_post(phs->sem->die);
    return (philos);
}
 
int            make_wait_thread(t_philo *phs)
{
    pthread_t    die;
    pthread_t    eat;
 
    if (pthread_create(&die, NULL, wait_for_die, (void *)phs))
        return (clear_all(phs, -1));
    if (pthread_create(&eat, NULL, wait_for_eat, (void *)phs))
        return (clear_all(phs, -1));
    pthread_join(die, NULL);
    return (SUCCESS);
}
 
cs