philosophers - 프로세스와 스레드 그리고 알고리즘

slee2·2021년 9월 3일
0

42Seoul

목록 보기
6/15
post-thumbnail

철학자에게 밥먹이는 서브젝트.

이 글은 philosphers 프로젝트가 어떤 프로젝트인지 알고 있다는 전제하에 쓰는 글입니다.

프로세스와 스레드

프로세스 : 운영체제로부터 자신만의 독립적인 고유 공간 + 자원을 할당 받아 동작합니다.

스레드 : 한 프로세스내에서 여러 흐름으로 동작하며 프로세스 내의 주소 공간이나 자원을 공유할 수 있습니다.

스레드를 생성할시, 마지막에 join 또는 detach 등의 함수를 사용하여 자원반납을 해줘야 한다. 이 프로젝트의 mandatory는 쓰레드 관리하는 방법을 bonus는 프로세스 관리하는 방법을 배운다는 느낌을 받았다.

프로세스와 스레드의 차이점

가장 큰 차이점은 자원 공유가 되냐 안되냐이다. 프로세스는 완전히 독립적이기 때문에 메모리 영역(Code, Data, Heap, Stack)을 다른 프로세스와 공유하지 않지만, 스레드는 해당 스레드를 위한 스택을 생성할 뿐 나머지(Code, Data, Heap)을 공유한다.

스택을 독립적으로 할당하는 이유

  • 스택은 함수 호출시 전달되는 인자, 되돌아갈 주소값 및 함수 내에서 선언하는 변수 등을 저장 하기 위해 사용되는 메모리 공간이다. 따라서 스택 메모리 공간이 독립적이란는 것은 독립적인 함수 호출이 가능하다는 것이고 실행흐름의 추가를 위한 최소 조건이 독립된 스택을 제공하는 것이다.

pthread_join


"동기화"를 시켜주며, 각 스레드들이 종료될때까지 기다린다. 내가 만든 철학자에서는 각 스레드가 while(1)을 돌기때문에 이 함수를 쓰지 않았다.

pthread_detach


"비동기화"를 시켜주며, 이 함수를 쓰면, 이 함수에 사용된 스레드를 분리시켜준다. 또, 이 분리된 스레드가 종료되는 즉시 모든 자원을 반납(free)시켜줄 것을 보증해준다.


스레드는 프로그램의 흐름이다. 이제 철학자 프로젝트를 진행하면서 스레드를 동기화로 관리할지, 비동기화로 관리할지, 적절히 섞어쓸지 선택해야한다. 나는 알아서 자원반납도 해주며, main이 돌아가는 동안에 모든 철학자를 스레드로 돌릴 수 있는 비동기화를 이용하여 프로젝트를 해결했다.

알고리즘

각 철학자와 이 철학자를 감시하는 모니터 스레드를 만들고 이를 detach를 이용해 스레드를 분리시켜 독립적으로 돌게한 후에 종료조건이 나오게 될때 메인 프로세스를 종료시켜 각 스레드를 종료시키는 방법으로 하였다.

모든 철학자는 아래의 행동을 반복한다. 포크를 잡고 먹고 자고 생각한다. 밑의 usleep(100)은 철학자의 순서도를 위한 시간을 약간 준것이다. 또, 포크를 집을때 0번 철학자는 0번 포크와 1번 포크, 1번 철학자는 1번 포크와 2번 포크, ... 마지막 철학자는 마지막 포크와 0번 포크를 집도록 구현했다.

while (1)
{
	get_fork(philo);
	eat(philo);
	sleeping(philo);
	usleep(100);
}


위 그림을 보면 시간이 지남에 따라 먼저 식사를 마친 철학자가 기다리고있는 철학자보다 먼저 포크를 집는 경우 발생했다.

그래서 위 코드의 마지막에 시간을 조금씩 준 이유가 위 그림처럼 기다리고 있던 철학자가 먼저 밥을 먹을 수 있도록 순서도를 맞춰주기 위해서이다. 근데 사실 저 부분은 구현을 어떻게 했냐에 따라 달리질 수도 있다. 또, 처음에 철학자들을 쓰레드로 생성할때도 시간을 조금씩 주면서 쓰레드를 생성하게 하여 순서도를 만들어줘야한다. 꼬이지 않고 차례대로 포크를 집게 하기 위함이다.

Mutex

공유된 자원의 데이터를 여러 쓰레드가 접근하는 것을 막는 것.

예를 들어 포크1가 뮤텍스로 생성되고 philo1 스레드가 포크1 뮤텍스를 lock시키면 다른 스레드들이 포크1 뮤텍스 lock부분으로 오게되면, philo1 스레드가 포크1 뮤텍스를 unlock시켜줄때까지 기다려야 하는 것이다.

뮤텍스를 쓰는 이유는 멀티 스레드가 서로 돌다보면 순서가 맞지않아 꼬이는 경우가 많다. 이를 방지하기 위해 순서도를 정하기 위함이다.


이 그림처럼 스레드 관리를 진행하였다. main스레드에서 mutex로 대기를 하고 있고, 철학자 스레드를 생성후 각 철학자 스레드 안에 모니터링을 위한 스레드를 각각 생성한다. 모니터링중에 철학자가 죽으면 main에서 대기를 하고 있는 뮤텍스를 해제하여 메인문을 진행하게 한다. 그렇게 메인문이 종료를 하게 되면 스레드들도 종료가 되게 한다는 알고리즘이다.

보너스

세마포어와 뮤텍스의 차이

세마포어(Semaphore) : 공유된 자원의 데이터를 여러 프로세스가 접근하는 것을 막는 것

뮤텍스(Mutex) : 공유된 자원의 데이터를 여러 쓰레드가 접근하는 것을 막는 것

즉, 스레드를 막냐, 프로세스를 막냐 차이이다. 보너스는 각 철학자들을 프로세스로 나눠서 다뤄야 하기 때문에 세마포어를 사용하는 것이다.

게다가 세마포어는 뮤텍스보다 더 편리하게 사용할 수 있는 것 같다. 뮤텍스는 하나의 포크를 하나의 뮤텍스로 만들어야 하지만, 세마포어는 처음부터 포크개수를 뮤텍스개수처럼 다룰 수 있다. 그래서 뮤텍스는 일일이 포크에 인덱스를 붙여 각 철학자에게 포크 번호를 잡는다면, 세마포어는 철학자가 포크를 잡았다고 가정하면 세마포어의 개수를 하나 감소시키는 방식이다.

이를 화장실로 많이 비유한다. (이해하기 편하긴함)

세마포어는 지하철 화장실처럼 화장실 칸의 개수가 여러개 유한하게 있는 곳의 열쇠의 갯수이다.

뮤텍스는 1개의 화장실에 대해서 다룬다면, 세마포어는 여러개의 화장실로 다루게 되고

화장실을 쓰기위해 열쇠를 하나 가져가면 P, 열쇠를 반납하면 V라고 한다.

만약 열쇠를 계속 가져가서(P) 열쇠가 0개가 되면, 다음 사람은 줄을 서서 기다려야한다.

화장실을 다 쓴다음 열쇠를 반납하게되면(V) 다음 사람이 들어갈 수 있다.

sem_wait(sem_t *sem);

세마포어의 P역할을 한다. 즉, 세마포어를 하나 감소시키는 역할을 하고 세마포어가 0일 경우에는 1이상이 될 때까지 스레드는 대기 상태에 있는다.

0이 아닐 경우에는 대기상태에서 빠져나와 세마포어를 또 1 감소시킨다.

sem_post(sem_t *sem);

세마포어의 V역할을 한다.

세마포어 값을 1 증가 시킨다.

보너스는 철학자를 프로세스로 다루기때문에 허용된 함수인 kill을 이용하여 각 철학자를 종료시킨다. 기본에서 종료조건이 나오면 메인을 종료시킨다는 알고리즘을 이용하여, 종료조건이 나오면 kill을 사용하여 각 프로세스를 종료시키는 방식이다.

0개의 댓글