필로소퍼! 는 철학자다. 다음은 철학자들이 프로그램에서 하는 행동이다.
- 한 명 이상의 철학자가 동그란 테이블에 앉아 테이블 한가운데 놓인 스파게티를 먹는다.
- 철학자는 먹고(eat), 자고(sleep), 생각(think)한다.
- 테이블에는 철학자의 수만큼의 포크가 있으며, 포크는 한 쌍의 철학자 사이에 하나씩 놓여있다.
- 스파게티를 먹기 위해서 철학자는 두 개의 포크를 사용해야 한다.
- 스파게티를 다 먹은 철학자는 테이블에 포크를 놓고 잠에 든다. 자고 일어나면 생각한다.
- 철학자는 굶지 않고 살아남아야 한다.
- 철학자끼리는 소통할 수 없다.
❗️ 데이터 레이스(data race)가 있으면 안된다. ❗️
💡 데이터 레이스(data race)란?
데이터 레이스는 멀티 스레드(또는 프로세스) 환경에서 여러 스레드(또는 프로세스)가 하나의 공유 자원에 동시에 접근할 때 발생하는 문제다. 즉, 하나의 스레드에서 이미 사용하고 있는 공유 자원에 다른 스레드가 또 접근하게 될 때 공유 자원의 상태가 동기화되지 않으면서 문제가 생기는 것이다.
이를 해결하기 위해 뮤텍스(Mutex) 또는 세마포어(Semaphore)를 이용해 작업 처리 순서를 제어하고 공유 자원에 대한 접근을 제어해야 한다.
결국 Philosophers는 "스레드(thread)"를 포크를 이용한 "뮤텍스(mutex)"로 "멀티 스레딩(multi-threading)" 하는 과제다. 이에 관련된 내용은 다음 포스트에서 다루도록 하겠다.
memset, printf, malloc, free, write, usleep, gettimeofday, pthread_create, pthread_detach, pthread_join, pthread_mutex_init, pthread_mutex_destroy, pthread_mutex_lock, pthread_mutex_unlock
예전에는 허용 함수가 많으면 좋았는데, 지금은... 걱정이 앞선다. 😭 처음 보는 함수들을 찬찬히 살펴보자 ㅎㅎ
- syntax :
#include <unistd.h> int usleep(useconds_t usec);
- description :
지정한 마이크로초 동안 대기 상태가 된다.- return :
성공 시0
실패 시-1
- syntax :
#include <sys/time.h> int gettimeofday(struct timeval *restrict tv, struct timezone *restrict tz);
- description :
소스로직 중 특정구간의 수행시간 차이를 계산하기 위해 사용된다. tz(timezone) 구조체는 더 이상 사용되지 않기 때문에 일반적으로NULL
로 지정하며, tv(timeval)는 현재 시스템 시간을 구하기 위한 구조체로 다음과 같은 형태다.struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
- return :
성공 시0
실패 시-1
- syntax :
#include <pthread.h> int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg);
- description :
해당 함수를 사용하는 프로세스에 새로운 스레드를 만들고 해당 스레드의 식별자를thread
포인터에 저장한다. 인자의start_routine
함수를arg
인자로 실행시키면서 시작된다.attr
인자는 스레드의 특성을 지정하기 위한 용도로 사용되는데,NULL
일 경우 기본 특성(joinable, non real-time)으로 지정된다.- return :
성공 시0
실패 시 에러코드
- syntax :
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);
- description :
thread
포인터가 가리키는 스레드가 종료되기를 기다린다. 해당 스레드가 종료되면 모든 자원을 반납한다.retval
포인터에는 스레드의 종료 상태가 저장된다. 스레드의 종료 코드를 알고 싶지 않다면NULL
을 넣으면 된다.- return :
성공 시0
실패 시 에러코드
- syntax :
#include <pthread.h> int pthread_detach(pthread_t thread);
- description :
thread
포인터가 가리키는 스레드를 분리시킨다. 분리된 스레드가 삭제될 때 해당 스레드의 자원은 자동으로 반납된다.- return :
성공 시0
실패 시 에러코드
- syntax :
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
- description :
mutex
인자가 가리키는 뮤텍스 객체를 초기화시킨다. 뮤텍스는 스레드가 공유하는 영역을 보호하기 위해 사용된다.attr
인자는 뮤텍스의 특징(fast, recurisve)을 정의하기 위해 사용되며,NULL
의 경우 기본값인 fast가 사용된다.- return :
성공 시0
실패 시 에러코드
- syntax :
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);
- description :
mutex
가 가리키는 뮤텍스 객체를 삭제하고 자원을 반납한다.- return :
성공 시0
실패 시 에러코드
- syntax :
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex);
- description :
임계 영역에 진입하기 위해 뮤텍스 잠금을 요청한다. 만약 뮤텍스의 최근 상태가 unlocked라면 스레드는 잠금을 얻고 임계 영역에 진입하게 되고 리턴한다. 만약 다른 스레드가 이미 뮤텍스 잠금을 얻은 상태라면 잠금을 얻을 수 있을 때까지 기다리게 된다.- return :
성공 시0
실패 시 에러코드
- syntax :
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);
- description :
뮤텍스 잠금을 되돌려 준다. fast 뮤텍스라면 언제나 unlocked 상태를 되돌려준다. recursive 뮤텍스라면 잠겨있는 뮤텍스의 수를 감소시키고 이 수가 0일 때 뮤텍스 잠금을 되돌려 준다.- return :
성공 시0
실패 시 에러코드
서브젝트에 대해 살펴봤으니 이제 프로세스와 스레드, 뮤텍스와 임계 영역에 대해 자세히 알아보자!
https://man7.org/linux/man-pages/man3/usleep.3.html
https://man7.org/linux/man-pages/man2/gettimeofday.2.html
https://man7.org/linux/man-pages/man3/pthread_create.3.html
https://man7.org/linux/man-pages/man3/pthread_detach.3.html
https://www.man7.org/linux/man-pages/man3/pthread_join.3.html
https://man7.org/linux/man-
https://www.delftstack.com/ko/howto/c/pthread_join-return-value-in-c/pages/man3/pthread_mutex_destroy.3p.html
https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html