42seoul/philosophers

스레드(thread)와 뮤텍스(mutex)

syom 2021. 6. 12. 04:58

서브젝트가 업데이트 되고, 우리는 식사하는 철학자 문제를

스레드(철학자)-뮤텍스(포크), 프로세스(철학자)-세마포어(포크) 로 구성되도록 코드를 짜야한다. 

같은 문제를 다른 방법들을 이용하여 코드를 짜기 위해서는 스레드와 프로세스, 뮤텍스와 세마포어의 차이를 이해하는게 좋다.

깔끔하고 정확한 설명들은 인터넷에 찾아보면 잘 정리되어있으니

이번 글에서는 내가 과제를 진행하면서 새로 알게 되었거나, 개념의 느낌(?) 위주대로 써 볼 생각이다.

스레드

스레드(thread)는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.
(출처 : 위키백과)

뮤텍스

컴퓨터 과학에서 락(lock) 또는 뮤텍스(mutex, 상호 배제에서)는 여러 스레드를 실행하는 환경에서 자원에 대한 접근에 제한을 강제하기 위한 동기화 매커니즘이다. 락은 상호 배제 동시성 제어 정책을 강제하기 위해 설계된다.

 

우선, 스레드는 같은 프로세스 안에서 동작한다.

너무 당연한 이야기지만, 과제를 진행하면서 스레드라는 개념을 뭔가 프로세스 안의 작은 프로세스가 있다고 생각이 들어서 

코드가 작동하는 순서에 따라가듯이 동시에, 그것도 만들어진 순서에 따라 동작하지 않을까? 라는 어렴풋한 생각을 했었다.

 

 

하지만 위키백과에 있던 이미지대로, 스레드는 어느 쪽이 확실히 먼저 동작한다고 확신할 수가 없었다.

thread 1의 어느 동작을 진행하다가 thread 2 의 동작을 하러 갈 수도 있는 것이다.

그렇기 때문에 우리는

1. 교착상태(deadlock)가 일어나는 것을 방지할만한 대책을 세워야 하고,

2. 철학자의 죽음이나 먹어야하는 식사 횟수 같은 현재 상태를 체크할 때 뒤섞이지 않도록 확실하게 순서를 보장해야 했다.

 

 모든 철학자들이 동시에 자신의 왼쪽에 있는 포크부터 잡는다면, 아무도 식사를 하지도 못하는 교착상태가 일어나게 된다.

교착상태를 방지할만한 대책은 홀수, 짝수 번호를 나누어 포크를 드는 순서를 다르게 하거나, 

포크에 번호를 매겼을 때, 둘 중 작은 포크를 먼저 집고, 마지막 번호의 철학자는 0번째의 포크를 먼저 집도록 하는 방법 같은 것들이 있다.

 

나는 홀수와 짝수를 나누는 방법을 선택했다.

말 그대로 홀수 번호인 철학자는 왼쪽 포크부터, 짝수 번호인 철학자는 오른쪽 포크부터 들게 하는 것이다.

 

.

.

 

교착상태를 막는 방법은 사실 서브젝트를 시작하기 전부터 여기저기서 많이 듣게되는 이야기라 쉽게 해결했으나,

막히는 부분은 다른 곳에 있었다.

철학자가 죽거나, 입력받은 먹어야하는 식사 횟수를 다 채우고 종료하도록 체크하는 end_flag 를 따로 설정했었는데

상태 메세지를 출력하는 함수 직전에 end_flag 를 체크해도 그 다음 메세지가 출력되어버리는 것이다.

마땅한 캡쳐본이 없어서 예시로만 사진을 찍어왔다...

예를들어 200ms 2 is eating 을 출력한 후에 먹어야하는 식사 횟수를 다 채우게 되었고 이제 멈추고 종료가 되어야하는데

동 시간대인 200ms 에 다른 스레드의 메세지가 출력이 된 후에 종료가 되는 것이다.

상태를 출력하는 함수에 들어가기 전에 체크를 하지만 말 그대로 printf 의 앞에 체크를 해도 결과는 마찬가지였다.

if (philo->info->end_flag)
	printf("%dms %d %s\n", timestamp, philo->num + 1, msg);

이 상황에서 나는 혹시 포크를 집기전에 플래그 체크를 못했나? 혹시 생각하기전에 플래그 체크를 못했나?

아니면 write 가 아니라 printf 를 써서 그런걸까? 라는 생각만 들어

무수하고 보기싫은 if 문들을 잔뜩 넣어봤지만 역시 해결은 안되었고

주변 친구들의 도움을 받아 알아낸 것은 상태를 체크하고 실행하는 부분은 순서가 명확해야한다 라는 것이였다.

 

아무리 출력이전에 end_flag 를 체크하더라도

end_flag 체크를 하고 printf 로 들어가기 직전에 다른 스레드로 넘어가서 end_flag 가 세워질 수도 있었다는 것이다.

여기서 다시 한번 스레드가 동작하는 순서가 정해져있지 않다는걸 느꼈다(?)

머리로만 대충 그렇구나 정도로만 생각했으나, 전혀 이해를 못하고 있었던 셈이다.

 

pthread_mutex_lock(&philo->info->print);
if (philo->info->end_flag)
	printf("%dms %d %s\n", timestamp, philo->num + 1, msg);
pthread_mutex_unlock(&philo->info->print);

 

이렇게 출력해주는 순서를 뮤텍스를 통해 보장해준다면,

(나는 아직 보지 못했지만)

printf 가 write에 비해 느리게 동작하고 버퍼에 쌓여있기 때문에 꼬이게 출력될 때가 있다고 하는데,

그 부분도 막아주고, 이 출력하는 부분이 확실하게 지금의 상태를 체크하고 실행될 수 있도록 하게 해준다.

'42seoul > philosophers' 카테고리의 다른 글

프로세스(process)와 세마포어(semaphore) 작성중  (0) 2021.06.12
philosopher 허용함수 정리  (0) 2021.06.06
philosopher subject  (1) 2021.06.06