Philosophers

필요한 변수:

  1. number_of_philosophers

    철학자 수(+ 포크의 수)

  2. time_to_die

    지난 번 식사로부터 식사를 마칠 때까지 남은 시간.

  3. time_to_eat

    밥 먹는 데 걸리는 시간. 두 개의 포크를 사용해야 한다.

  4. time_to_sleep

    잠자는 데 걸리는 시간.

  5. number_of_times_each_philospher_must_eat

    옵션이며, 식사 횟수가 이 변수의 값과 같아지면 프로그램 종료.


Program 1 - 이해가 필요한 함수들

usleep

gettimeofday

pthread_create

pthread_detach

pthread_join

pthread_mutex_init

pthread_mutex_destroy

phthread_mutex_lock

pthread_mutex_unlock



usleep

설명

usleep은 코드 실행을 늦추는 함수이다.

헤더는 <unistd.h>,

함수의 형태는 void usleep(unsignd long useconds) 이다.

마이크로 초를 기반으로 하며, 밀리초로 바꾸려면 원하는 밀리초 * 1000 을 해주면 된다.

(참고로 1000밀리초는 1초, 1000 * 1000 마이크로초도 1초이다.)


ex

#include <stdio.h>
#include <unistd.h>

int main()
{
  while (true)
  {
    printf("1초마다 문자열을 출력합니다\n");
    usleep(1000*1000);
  }
}

// ./a.out 으로 실행시 1초마다 메시지가 출력된다.

ref - http://forum.falinux.com/zbxe/index.php?mid=C_LIB&document_srl=564661




gettimeofday


설명

gettimeofday() 함수는 마이크로초 단위의 시간을 되돌려준다.


헤더 및 형식

#include <sys/time.h>
#include <unistd.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

첫 번째 인자인 tv는 현재 시스템 시간을 저장하기 위한 구조체이다.

두 번째 인자인 tz 는 타임존을 설정하기 위해 사용되는 구조체이다.

해당 구조체들은 다음과 같은 값을 갖는다.

struct timeval // tv
{
  long tv_sec;    // 초
     int tv_usec; // micro초
}

struct timezone //tz
{
  int tz_minuteswest; // 그리니치 기준 서측으로 분(minute) 차(difference)
  int tz_dsttime; // DST 보정타입. 일광 절약시간...?
}

(정말 다행히도(?) tz는 현재 사용되지 않아서 NULL로 처리하는 게 일반적이라고 한다.)


반환값

성공 시 0, 실패 시 -1 리턴.


예제1

#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main()
{
        struct timeval mytime;

        gettimeofday(&mytime, NULL);
        printf("%ld:%d\n", mytime.tv_sec, mytime.tv_usec);
        printf("%ld:%d\n", mytime.tv_sec * 1000000, mytime.tv_usec);
        printf("relative time is: %ld\n", mytime.tv_sec * 1000000 + mytime.tv_usec);
        return (0);
}
/*
1618818045:394638
1618818045000000:394638
relative time is: 1618818045394638
*/

예제2

#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    struct timeval startTime, endTime;
    double diff_tv_sec;
    double diff_tv_usec;

    gettimeofday(&startTime, NULL);
    usleep(1000 * 1000);
    gettimeofday(&endTime, NULL);
    diff_tv_sec = ( endTime.tv_sec - startTime.tv_sec );
    diff_tv_usec = ( endTime.tv_usec - startTime.tv_usec ) / 1000000;
    printf("%f seconds\n", diff_tv_sec); // f는 (double로) 형변환해 출력.
    printf("%f micro seconds\n", diff_tv_usec);
    return 0;
}

// 결과
// 1.000000 seconds
// 0.000000 micro seconds

필자는 과제에서 이렇게 사용!

size_t    relative_time(size_t time_start)
{
    struct timeval    current;

    gettimeofday(&current, 0);
    return (current.tv_sec * 1000 * 1000 + current.tv_usec - time_start);
}

ref -

https://www.joinc.co.kr/w/man/2/gettimeofday

https://mozi.tistory.com/127




pthread_create

pthread

pthread란, POSIX thread의 약자로 유닉스 계열 POSIX 시스템에서 스레드를 편하게 만들 수 있게 도와주는 API이다.

헤더 및 사용형식

pthread사용을 위해서는 <pthread.h> 를 include 해야 한다.

그리고 pthread_create는 쓰레드를 생성하는 함수로서 다음의 형식을 갖는다.

int pthread_create(phtread_t *thread, const phtread_attr_t *attr, void *(*start_routine)(void *), void *arg);


여기서 첫 번째 매개변수인 pthread_t *thread는 쓰레드 식별자로서 생성된 스레드를 담을 쓰레드의 주소 정도로 생각하면 되겠다.

두 번째 매개변수인 const phread_attr_t *attr은 쓰레드 특성을 지정하기 위해 이용하는데, 대개 NULL 처리 해버리니 크게 신경쓰지 않아도 될 듯하다.

세 번째 매개변수인 void *(*start_routine)(void *)은 thread가 실행되었을 때 시작할 함수이다.

네 번째 매개변수인 void *arg는 세 번째 매개변수인 함수의 인자로 들어갈 인자이다.


pthread_create를 한번 실행시키면 하나의 스레드가 추가로 생성되기 때문에 총 두 개의 쓰레드(main쓰레드, 새로 생성한 쓰레드)가 동작하게 된다.


예제

이제 예제를 통해 한번 살펴보자.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>


void *p_function(void * data);

int main(void)
{
  pthread_t pthread;
  int thr_id;
  int status;
  char p1[] = "thread_created";
  char p2[] = "thread_main";

  thr_id = pthread_create(&pthread, NULL, p_function, (void*)p1);
  if(thr_id < 0)
  {
    perror("pthread0 create error");
    exit(EXIT_FAILURE);
  }

  p_function((void *)p2);

  printf("created thread id: %x\n", pthread);
  printf("end\n");
  return 0;
}

void *p_function(void * data)
{
  pthread_t tid; // thread id

  tid = pthread_self();

  char* thread_name = (char *)data;
  int i = 0;

  while(i < 3)
  {
    printf("thread name : %s, tid : %x\n", thread_name, (unsigned int)tid);
    i++;
    usleep(1000 * 1000);
  }
}

위 예제에서는 pthread_create를 통해 쓰레드를 생성함과 동시에 p_function함수를 호출하여 인자로 준 thread_name과 생성된 thread_id를 출력하고 있다. 여기서 usleep1초를 기다리게 만드는데, 어떤 쓰레드에서 usleep이 호출되면 다른 쓰레드에서 일을 하게 된다. 결국 main_thread와 우리가 생성한 thread가 번갈아가며 실행되는 것이다.


결과는 다음과 같다.(컴파일: gcc pthread_create.c -pthread)

thread name : thread_main, tid : 12b75dc0
thread name : thread_created, tid : 65a8000
thread name : thread_main, tid : 12b75dc0
thread name : thread_created, tid : 65a8000
thread name : thread_main, tid : 12b75dc0
thread name : thread_created, tid : 65a8000
created thread id: 65a8000
end

ref - https://m.blog.naver.com/whtie5500/221692793640



pthread_join


헤더 및 사용형식

#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

설명

특정 쓰레드가 종료되기까지 기다린 후 쓰레드가 종료되면 실행한다.

첫 번째 매개변수는 thread이고, 두 번째 매개변수는 해당 쓰레드 함수의 리턴값이 된다.

join된 쓰레드는 반납된 쓰레드로 간주하며, 모든 자원을 반납하게 된다.


예제 살펴보기

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *t_function(void *param)
{
    printf("쓰레드 함수 실행\n");
    for (int i = 1; i <= 5; i++)
    {
        usleep(1000 * 1000);
        printf("쓰레드 함수 실행 중..(%d / 5)\n",i);
    }
        printf("쓰레드 함수 종료\n");
        return (void *)2147483647;  // result에 담길 값.
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int result;

    thr_id = pthread_create(&p_thread, NULL, t_function, NULL);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    // 쓰레드 식별자 p_thread 가 종료되길 기다렸다가 
    // 종료후에 리턴값을 받아온다.
    pthread_join(p_thread, (void *)&result);
    printf("thread join 실행됨: %d\n", result);

    printf("main() 종료\n");

    return 0;
}

결과

쓰레드 함수 실행
쓰레드 함수 실행 중..(1 / 5)
쓰레드 함수 실행 중..(2 / 5)
쓰레드 함수 실행 중..(3 / 5)
쓰레드 함수 실행 중..(4 / 5)
쓰레드 함수 실행 중..(5 / 5)
쓰레드 함수 종료
thread join 실행됨: 2147483647
main() 종료

ref -

https://bitsoul.tistory.com/160


pthread_detach

헤더 및 사용형식

# include <pthread.h>

int phtread_detach(phtread_t th);

설명

특정 쓰레드를 해제한다.

일반적으로 pthread_create를 통해 생성한 쓰레드는 쓰레드 종료 시점에 자동으로 자원이 해제되는 게 아니다.

이를 해제해줄 수 있는 것이 pthread_join과 pthread_detach이다.


pthread_join vs phtread_detach 🌟

pthread_join과 pthread_detach 의 차이는 join의 경우 쓰레드가 종료될 때까지 기다린다는 것이고, detach는 만나는 즉시 쓰레드를 종료시켜버린다는 것이다. 그리고 더 중요한 차이는 join은 쓰레드가 종료될 때까지 기다리므로 다른 쓰레드가 실행하는 함수가 이루어질 수 없을 수 있다는 것이고 detach는 다른 쓰레드가 실행하는 함수가 실행될 수 있다는 것이다.



좀 더 직관적으로 이해하기 위해 아래 예제를 한 번 살펴보자.


예제

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *t_function(void *param)
{
    for (int i = 1; i <= 5; i++)
    {
        usleep(1000); 
        // 여기서 main쓰레드로 넘어감.
        // main에서는 그 다음 명령이 detach이므로
        // 그 시점에 바로 쓰레드 종료.
        printf("쓰레드 함수 실행 중..%d/5\n",i);
    }
        printf("쓰레드 함수 종료\n");
        return (void *)2147483647;
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int result;

    thr_id = pthread_create(&p_thread, NULL, t_function, NULL);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    } 
    pthread_detach(p_thread);

    printf("main() 종료\n");

    return 0;
}

결과

main() 종료

결과가 저 한 줄만 출력된 이유는 Thread함수에서 usleep이 호출될 때 main쓰레드로 넘어가게 되는데 main쓰레드에서는 detach를 만나자 마자 생성한 thread를 종료 및 반납해버리므로 main() 종료 라는 문구만 출력되고 있다.

만약 join을 썼다면 아래와 같은 문구가 출력되었을 것이다.

쓰레드 함수 실행 중..1/5
쓰레드 함수 실행 중..2/5
쓰레드 함수 실행 중..3/5
쓰레드 함수 실행 중..4/5
쓰레드 함수 실행 중..5/5
쓰레드 함수 종료
main() 종료

그런데 detach와 join의 차이를 확인할 수 있는 더 중요한 예제는 바로 다음이다.

일단 예제를 다음과 같이 바꾸었다.

main문이 종료되지 않게 무한루프를 추가했고, 쓰레드를 두 개로 나누었다.

아래 예제를 돌려보고 결과를 확인해볼텐데, 그 결과에 대해 미리 요약하자면

join은 쓰레드가 끝나기 전까지 main 쓰레드가 동작할 수 없게 만들고, detach는 동작할 수 있게 만든다 는 것이다.


예제1 - detach

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *t_function(void *param)
{
    for (int i = 1; i <= 5; i++)
    {
        usleep(1000 * 1000 * 2); //2초 대기
        printf("%s: ", (char *)param);
        printf("쓰레드 함수 실행 중..%d/5\n",i);
    }
        printf("쓰레드 함수 종료\n");
        return (void *)2147483647;
}

int main()
{
    pthread_t p_thread1;
    pthread_t p_thread2;
    int thr_id1;
    int thr_id2;

    thr_id1 = pthread_create(&p_thread1, NULL, t_function, "thread1");
    thr_id2 = pthread_create(&p_thread2, NULL, t_function, "thread2");
    if (thr_id1 < 0 || thr_id2 < 0)
    {
        perror("thread create error : ");
        exit(0);
    } 
    pthread_detach(p_thread1);
    pthread_detach(p_thread2);
    //  pthread_join(p_thread1, 0);
  //  pthread_join(p_thread2, 0);

    int s = 0;
    while (42)
{
    printf("%d초 경과\n", s++);
    usleep(1000 * 1000);
}

    printf("main() 종료\n");
    return 0;
}

결과 : main 쓰레드가 돌리는 while(42) 부분과 t_function이 동시에 돌아간다.

0초 경과
1초 경과
thread1: 쓰레드 함수 실행 중..1/5
thread2: 쓰레드 함수 실행 중..1/5
2초 경과
3초 경과
thread1: 쓰레드 함수 실행 중..2/5
4초 경과
thread2: 쓰레드 함수 실행 중..2/5
5초 경과
thread1: 쓰레드 함수 실행 중..3/5
6초 경과
thread2: 쓰레드 함수 실행 중..3/5
7초 경과
thread1: 쓰레드 함수 실행 중..4/5
thread2: 쓰레드 함수 실행 중..4/5
8초 경과
9초 경과
thread1: 쓰레드 함수 실행 중..5/5
쓰레드 함수 종료
thread2: 쓰레드 함수 실행 중..5/5
쓰레드 함수 종료
10초 경과
11초 경과
12초 경과
13초 경과
...

예제2. - join. (위 예제에서 주석의 위치만 바뀜.)

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *t_function(void *param)
{
    for (int i = 1; i <= 5; i++)
    {
        usleep(1000 * 1000 * 2); //2초 대기
        printf("%s: ", (char *)param);
        printf("쓰레드 함수 실행 중..%d/5\n",i);
    }
        printf("쓰레드 함수 종료\n");
        return (void *)2147483647;
}

int main()
{
    pthread_t p_thread1;
    pthread_t p_thread2;
    int thr_id1;
    int thr_id2;

    thr_id1 = pthread_create(&p_thread1, NULL, t_function, "thread1");
    thr_id2 = pthread_create(&p_thread2, NULL, t_function, "thread2");
    if (thr_id1 < 0 || thr_id2 < 0)
    {
        perror("thread create error : ");
        exit(0);
    } 
    //pthread_detach(p_thread1);
    //pthread_detach(p_thread2);
      pthread_join(p_thread1, 0);
    pthread_join(p_thread2, 0);

    int s = 0;
    while (42)
{
    printf("%d초 경과\n", s++);
    usleep(1000 * 1000);
}

    printf("main() 종료\n");
    return 0;
}

결과: (main 쓰레드가 돌아가지 않는다 !)

thread1: 쓰레드 함수 실행 중..1/5
thread2: 쓰레드 함수 실행 중..1/5
thread1: 쓰레드 함수 실행 중..2/5
thread2: 쓰레드 함수 실행 중..2/5
thread1: 쓰레드 함수 실행 중..3/5
thread2: 쓰레드 함수 실행 중..3/5
thread1: 쓰레드 함수 실행 중..4/5
thread2: 쓰레드 함수 실행 중..4/5
thread1: 쓰레드 함수 실행 중..5/5
쓰레드 함수 종료
thread2: 쓰레드 함수 실행 중..5/5
쓰레드 함수 종료
0초 경과
1초 경과
2초 경과
3초 경과
4초 경과
...

이 둘의 차이를 비교하는 것은 philosopher 과제를 하는 데에 있어서 필수다.

한번 잘 생각해보고 어떻게 사용할 수 있을지 알아내보자.


ref -

https://www.joinc.co.kr/w/man/3/pthread_detach

https://bitsoul.tistory.com/161


pthread_mutex_init & phthread_mutex_lock & pthread_mutex_unlock

mutex란?

mutex는 여러 개의 쓰레드가 공유하는 데이터를 보호하기 위해 사용되는 도구이다.

어떤 코드영역에 대해 한 번에 하나의 쓰레드만 실행가능하도록 만듦으로써 공유되는 자원을 공유한다.

무슨 말인지 아직 이해가 가지 않을 것이다.

아래 예제를 통해 좀더 살펴보자.

참고로 하나의 쓰레드만 접근할 수 있는 영역을 critical section 이라고 한다.


pthread_mutex_init

먼저 mutex를 init하는 것부터 알아보자. 이것을 해주어야 mutex를 사용할 수 있게 된다.

phtread_mutex_init 함수를 사용한다.

#include <pthread.h>

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

파라미터

pthread_mutex_t *mutex

초기화 할 mutex 객체

const pthread_mutexattr_t * attr

mutex의 특성을 맞추기 위해 설정함. 기본적으로 NULL임.

종류로 fast, recurisev, error checking이 있는데 디폴트 값은 fast이다.


에러 ERROR

  • EINVAL : attr이 이상한 값이 들어옴
  • ENOMEM : 다른 뮤텍스에서 사용한 쓰레드

TIP

  • 정적으로 할당하고 싶을 땐 아래와 같이 실행하면 됨

    pthread_mutex_t mutex = PTHREAD_MUTX_INITIALIZER;

Pthread_mutex_lock & unlock

이 두 가지 함수는 mutex 임계 구역 진입 시 그 코드 구역을 잠그고 여는 역할을 해주는 함수들임.

한 쓰레드에서 lock함수를 이용해 닫아줬다면 다른 코드는 그 구역에 들어가기 위해 unlock할 때까지 기다려야 한다.


#include <pthread.h>

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

인자인 mutex는 잠그거나 해제시켜줄 쓰레드.


에러

  • pthread_mutex_lock
    • 에러 시 에러코드
      • EINVAL : mutex 유효하지 않음
      • EDEADLK : 데드락 조건이 발생
  • pthread_mutex_unlock
    • 에러 시 에러코드
      • EINVAL : mutex 유효하지 않음
      • EPERM : 현재 쓰레드가 잠겨져있지 않음

예제

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

pthread_mutex_t mutex;
int cnt = 0;

void *count(void *arg){
    int i;
    char* name = (char*)arg;

    // pthread_mutex_lock(&mutex);

    //======== critical section =============
    cnt = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%s cnt: %d\n", name,cnt);
        cnt++;
        usleep(1);
    }
    //========= critical section ============
    // pthread_mutex_unlock(&mutex);
}

int main()
{
    pthread_t thread1,thread2;

    pthread_mutex_init(&mutex,NULL);

    pthread_create(&thread1, NULL, count, (void *)"thread1");
    pthread_create(&thread2, NULL, count, (void *)"thread2");

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&mutex);
}

위 예제 코드를 실행하면 아래와 같이 나오는데, phtread_mutex_lock, unlock 부분의 주석을 해제하고 실행한 결과를 비교하면 다음과 같다.


// 주석 해제 전
thread2 cnt: 0
thread1 cnt: 0
thread2 cnt: 1
thread1 cnt: 3
thread2 cnt: 4
thread1 cnt: 5
thread2 cnt: 5
thread1 cnt: 7
thread2 cnt: 8
thread2 cnt: 9
thread1 cnt: 9
thread2 cnt: 10
thread1 cnt: 11
thread2 cnt: 13
thread1 cnt: 14
thread2 cnt: 15
thread1 cnt: 16
thread2 cnt: 17
thread1 cnt: 18
thread1 cnt: 19

// 주석 해제 후 (mutex_lock, unlock 적용 후)
thread2 cnt: 0
thread2 cnt: 1
thread2 cnt: 2
thread2 cnt: 3
thread2 cnt: 4
thread2 cnt: 5
thread2 cnt: 6
thread2 cnt: 7
thread2 cnt: 8
thread2 cnt: 9
thread1 cnt: 0
thread1 cnt: 1
thread1 cnt: 2
thread1 cnt: 3
thread1 cnt: 4
thread1 cnt: 5
thread1 cnt: 6
thread1 cnt: 7
thread1 cnt: 8
thread1 cnt: 9


그 외) Semaphore와 Process

Semaphore에 대해 잘 정리해놓은 joockim의 노션을 소개한다.

여기 링크를 클릭하면 들어갈 수 있다.

그리고 바로 다음 글에서는 seongwpa님이 정리하신 semaphore + process 글에 살짝의 내 생각만 얹은 글을 쓸 예정이다.

다음 글에서 만나요우~


ref-

https://reakwon.tistory.com/98

joockim notion


'Code 42 > Philosophers' 카테고리의 다른 글

42 Philosophers(철학자 문제) 필수지식 정리 - 2  (0) 2021.05.01
Philosophers 해석  (0) 2021.02.09
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기
// custom