퐁키조아 2021. 12. 29. 00:07

1. Race Condition in thread

아래 예제는 pthread_create 을 통해서 do_loop1, do_loop2 스레드를 생성하여 각 스레드에서 ncount를 증가/감소 하는 기능을 한다. 이때 실행 결과로 0이 나올 것을 예상하겠지만, 스레드끼리 ncount 값이 동기화가 안된 경우에는 0이 나오지 않을 수도 있다. 즉, 스레드끼리 Race Condition이 발생할 수 있으므로 Thread Synchronization 이 필요하다.

volatile int ncount;

int main(int argc, char *argv[])
{
  ncount = 0;
  // do_loop1, do_loop2 thread Create, and Join
  printf("counter = %d", ncount);
  return 0;
}

void* do_loop1(void *data)
{
  int i, *loop = (int *)data;
  for (i = 0; i < *loop; i++)
  	ncount++;
  return NULL;
}

void* do_loop2(void *data)
{
  int i, *loop = (int *)data;
  for (i = 0; i < *loop; i++)
    ncount--;
  return NULL;
}
  • Critical Section: 공유 변수
  • Mutual Exclusion: 하나의 스레드에서 critical section에 접근하는 것을 보장 하는 것
  • Mutex: 스레드 동기화 기법 중 하나

 

2. Creating, Destroying a Mutex

$\verb|pthread_mutext_t mutex = PTHREAD_MUTEX_INITIALIZER|$;

Static Initialization: 선언과 동시에 초기화

 

$\verb|int pthread_mutex_init(*mutex, *attr)|$

Dynamic Initialization: 선언 이후 초기화

 

$\verb|int pthread_mutex_destroy(*mutex)|$

Mutex 삭제하기

 

3. Locking, Unlocking a Mutex

$\verb|int pthread_mutex_lock(*mutex)|$

Mutex를 Lock 하는 시스템 콜 (화장실 칸에 문 잠그고 들어간 것 - 닫혀 있으면 열릴때까지 대기)

 

$\verb|int pthread_mutex_trylock(*mutex)|$

Mutex를 Lock 하려고 시도하는 시스템 콜 (화장실 칸이 열려 있으면 들어가고, 닫혀 있으면 그냥 리턴)

 

$\verb|int pthread_mutex_unlock(*mutex)|$

Mutex를 Unlock 하는 시스템 콜

 

4. Mutex로 Race Condition 해결하기

1) for 문 내부 Mutex Lock

이 경우 실행 시간이 매우 길어지고, ncount를 증가/감소시키는 작업이 각 스레드별로 일어나기 때문에 병렬적으로 실행되진 않는다. (++되는 동안 --가 될수는 없음)

volatile int ncount;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* do_loop1(void *data)
{
  int i, *loop = (int *)data;
  for (i = 0; i < *loop; i++) {
  	pthread_mutex_lock(&mutex);
  	ncount++;
  	pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

void* do_loop2(void *data)
{
  int i, *loop = (int *)data;
  for (i = 0; i < *loop; i++) {
  	pthread_mutex_lock(&mutex);
  	ncount--;
  	pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

 

2) for 문 외부 Mutex Lock

Mutex_lock, unlock 의 호출 횟수는 1회로 감소한다. 하지만 두 스레드 간의 병렬적 관점으로 봤을 때는 효율이 매우 떨어진다. - for문 두 개가 큰 덩어리로 나뉘어서 실행된다!

volatile int ncount;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* do_loop1(void *data)
{
  int i, *loop = (int *)data;
  pthread_mutex_lock(&mutex);
  for (i = 0; i < *loop; i++) {
  	ncount++;
  }
  pthread_mutex_unlock(&mutex);
  return NULL;
}

void* do_loop2(void *data)
{
  int i, *loop = (int *)data;
  pthread_mutex_lock(&mutex);
  for (i = 0; i < *loop; i++) {
  	ncount--;
  }
  pthread_mutex_unlock(&mutex);
  return NULL;
}

 

5. Deadlock

컴퓨터 과학자 데이크스트라가 고안한 ‘철학자의 만찬 문제’는 컴퓨터 내에서 여러 프로세스가 서로 점유하고 있는 자원을 얻기 위해 상대방의 작업이 끝나기만을 기다리며 대기하는 ‘교착 상태’와 특정 프로세스가 원하는 자원을 계속 할당받지 못하는 ‘기아 상태’ 에 대한 대표적인 예시로 활용된다.
<그림1>처럼 다섯 명의 철학자($P_1$~$P_5$)가 앉아 있는 자리 왼 쪽에 포크($f_1$~$f_5$)가 각각 하나씩 놓여 있다고 하자. 가운데 놓인 스파게티를 덜어 오기 위해서 철학자는 양옆의 포크를 동시에 이용해야 하며, 다른 철학자가 사용 중인 포크는 사용할 수 없다. 또 모든 철학자 $P_n$이 왼쪽의 포크 $f_n$을 먼저 든 다음 오른쪽 포크 $f_{n+1}$을 들어서 스파게티를 가져오기로 약속했을 때, 스파게티를 가져오기 위해 모든 철학자가 동시에 왼쪽 포크를 들게 되면, 오른쪽에는 남는 포크가 없어 모두가 무한정 서로를 기다리는 교착 상태(Deadlock)가 발생한다. 또한 교착 상태가 해결되더라도 여러 이유로 특정 철학자에게 포크를 들 기회가 주어지지 않을 경우 특정 철학자만 스파게티를 먹지 못하는 기아 상태가 발생한다. 컴퓨터에서도 마찬가지로 CPU나 메모리 등과 같은 공유 자원을 이용해 프로세스를 처리하는 과정에서 다양한 이유로 교착 상태와 기아 상태가 발생하게 된다.

- 2021년 7월 교육청 모의고사 국어 (철학자들의 만찬 문제)

Deadlock 해결법

  1. 포크를 한개 더 많게 배치
  2. 철학자들이 동시에 왼쪽과 오른쪽 포크를 들도록 제한 (병렬성이 떨어진다.)
  3. 홀수번째 사람은 왼쪽 먼저, 짝수 번째는 오른쪽 먼저 포크를 들도록 제한 (홀수명일 경우엔 깍두기 한명이 생겨서  불공평한 일이 생길 수가 있다.)
  4. Deadlock이 해결되어도 Starvation 이 발생할 수 있다.

 

Mutex 를 이용하여 3번 구현하기

void pickup(Phil_struct *ps)
{
  if (ps->id % 2 == 1) {
    pthread_mutex_lock(pp->lock[ps->id]);       /* lock up left stick */
    pthread_mutex_lock(pp->lock[(ps->id+1)%phil_count]); /* lock right stick */ 
  } else {
    pthread_mutex_lock(pp->lock[(ps->id+1)%phil_count]); /* lock right stick */ 
    pthread_mutex_lock(pp->lock[ps->id]);       /* lock up left stick */
  }
}

// Mutex: pickup의 역순으로 unlock 해줘야 한다.
void putdown(Phil_struct *ps)
{
  if (ps->id % 2 == 1) {
    pthread_mutex_unlock(pp->lock[(ps->id+1)%phil_count]); /* unlock right stick */ 
    pthread_mutex_unlock(pp->lock[ps->id]);  /* unlock left stick */
  } else {
    pthread_mutex_unlock(pp->lock[ps->id]);  /* unlock left stick */
    pthread_mutex_unlock(pp->lock[(ps->id+1)%phil_count]); /* unlock right stick */ 
  }
}

 

6. Siganl과 스레드

멀티 스레드 프로세스에서 $\verb|SIGTERM|$ 시그널을 받으면 어떻게 될까? 

$\rightarrow$ 모든 스레드가 다 종료된다! 

 

특정 스레드에게 시그널을 보낼 수 있을까?

$\rightarrow$ $\verb|pthread_kill(tid, SIGNUM)|$ 을 통해 보낼 수 있다. 하지만 다른 프로세스의 스레드한테 시그널을 보낼 수는 없다. 프로세스 때 배운 시그널을 보내는 $\verb|kill()|$ 을 사용하면 어떤 스레드에서 실행이 될지 모른다.