philosopher

junpkim·2022년 5월 20일
0

개념

식사하는 철학자 문제를 해결하는 philosopher 과제를 하게 되었다.
같은 자원을 점유하려는 프로세스들간 발생할 수 있는 교착상태에 대한 개념어쩌구 워낙 유명한 문제이기도 하고 잘 설명되어 있는 게시물들이 많으니 설명은 링크로 대체.

아무튼 철학자들을 스레드로 구현하고, 두 철학자들이 모두 접근이 가능하지만 사용하는건 한 명만 가능한 '포크'라는 자원을 만들어야한다.

스레드

스레드는 cpu에서 작업을 수행할 수 있는 최소 단위이다. 즉 최소 1스레드는 있어야 뭐라도 할 수 있단 것.
그리고 기본적으로 프로그램을 실행하면 스레드 하나를 할당해준다. 우리는 여기서 철학자 수 만큼 스레드를 더 할당해줄 것이다.

프로세스

프로그램. 우리가 작업 관리자(window os)키고 두번째 탭 들어가면 볼 수 있는 그 프로세스 맞다.
운영체제에서 나누는 단위이다.

Context switching

한 대의 컴퓨터에서 실행되는 프로세스 수는 많고 CPU의 스레드 개수는 한정되어있다. 그럼 많은 프로세스들이 어떻게 동시에 실행될 수 있냐? 그야 CPU가 엄청 빠르기 때문에 사용할 수 있는 시간을 쪼개서 프로세스들에게 할당해줘도 우리가 보기엔 동시에 실행되는 것 처럼 보인다.

CPU가 하던 일을 멈추고 다른 일로 바꾸는 작업을 context switching이라고 하는데 이는 프로세스가 할당된 시간을 다 사용했을 때나 인터럽트가 발생했을 경우 행하게 된다.

사람들이 책 읽다가 중간에 다른 일 해야되면 책갈피를 꽂는것 처럼 CPU도 context switching을 하기 전에 프로세스의 정보를 저장하고 다음 프로세스의 정보를 불러오는데 이 정보가 저장되는 책갈피를 PCB(Process Control Block)라고 한다.

요다쨩이 CPU이고 줄 서있는 팬들이 프로세스로 비유할 수 있겠다. 팬에게 할당 된 시간이 다 되면 다른 팬에게 순서가 넘어가고 하던 말을 마저 하려면 다시 줄을 서야한다. 만약 팬이 이상행동(인터럽트)을 한다면 역시 제지당할 것이다. 요다쨩과 팬 사이에는 PCB가 없어서 하던 말을 기억하진 못할것 같지만 아무튼 그렇다.

풀이

잠깐 딴 길로 샜는데 다시 philosopher 과제로 돌아오면, 우리는 하나의 프로세스에 철학자의 수 만큼 스레드를 할당하고 동시접근 못하도록 포크를 mutex라는 걸로 만들어 줄 것이다.
mutex는 해당 자원에 하나의 스레드만 접근 할 수 있도록 한다.
이 mutex는

int pthread_mutex_init(pthread_mutex_t *mutex)
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_destroy(pthread_mutex_t *mutex)

위 함수를 사용해 제어할 수 있는데 함수명이 아주 직관적이므로 설명 생략.

만약 mutex가 이미 다른 스레드에 의해 lock 되어 있는 경우 해당 mutex에 접근하려던 스레드는 unlock 될 때까지 기다린다.
lock 되어 있는 mutex를 destroy 해도 unlock 되지 않기 때문에 기다리던 스레드는 계속 기다리므로 주의가 필요하다.

철학자가 포크를 잡는 행위를 lock, 음식을 다 먹고 포크를 놓는 행위를 unlock로 구현하면 된다.

교착상태를 방지하기 위해 짝수 번째의 철학자는 시작할 때 지연을 주고 시작하면 홀수 번째랑 짝수 번째랑 알아서 번갈아가며 먹는다.

그리고 여러 스레드에서 한번에 출력을 하다보니 이렇게 뒤죽박죽으로 출력이 된다. 이를 방지하기 위해 출력을 위한 mutex를 하나 더 생성해주어야 한다.

보너스

보너스 파트는 포크가 중앙에 존재하고, 포크들은 하나의 semaphore로 구현되어야 한다.
semaphore는

sem_t *sem_open(const char *name, int oflag)
sem_t *sem_open(const char *name, int oflag, mode_t, unsigned int value)
int sem_wait(sem_t *sem)
int sem_post(sem_t *sem)
int sem_unlink(const char *name)
int sem_close(sem_t *sem)

위 함수로 제어할 수 있는데, sem_open은 mutex_init, sem_wait은 mutex_lock, sem_post는 mutex_unlock, sem_unlink는 mutex_destroy에 대응된다.
그럼 sem_close는? 얘는 좀 특이한데 해당 semaphore를 참조하는 스레드나 프로세스가 하나라도 존재하면 sem_unlink를 수행하지 않는다. 근데 프로세스가 열려있으면 메인스레드가 semaphore를 참조하고 있기 때문에 프로세스 종료 전에는 sem_unlink가 이루어지지 않는다(프로세스 종료시 자동으로 sem_close가 수행 됨). 그래서 unlink 해주려면 sem_close를 통해 참조 값을 0으로 만들어주어야 한다.

아 그리고 semaphore는 파일 시스템에 파일 형태로 존재한다.
매개변수 4개를 받는 sem_open 함수를 보면 oflag, mode_t를 인자로 받는데, 파일 오픈할 때 사용했던 O_CREAT와 O_EXCL를 flag로 줄 수 있다. 다른 옵션은 무시 됨. mode_t는 해당 semaphore에 접근할 수 있는 권한을 의미한다. default 값은 0644
O_CREAT flag를 주는 경우 semaphore가 존재하지 않을 때만 semaphore를 생성한다. 이미 동일한 name의 semaphore가 있으면 있던걸 그대로 쓴다. 그래서 sem_unlink 안해주고 강제종료 해주고, 다시 실행하면 기존 프로세스에서 semaphore를 참조하고 있다고 인식하기 때문에 새로운 프로세스에서는 참조하지 못한다.
때문에 sem_open 다음 바로 sem_unlink 코드를 작성해주면 알아서 sem_close가 이루어졌을 때 unlink가 된다!

0개의 댓글