#경쟁조건 #임계영역 #상호배재 #원자성

thread vs process

쓰레드는 프로세스처럼 자기만의 PC값과 레지스터 값을 가지고 각각의 스택을 가지고 있다. 즉 각각의 쓰레드는 독립적인 context를 갖고있다. 하지만 프로세스와 달리, 한 프로세스안의 여러 쓰레드들은 메모리공간을 공유한다. 멀티 쓰레드인 경우 주소 공간에 각 쓰레드의 스택이 할당되어 있다.

1️⃣ Race Condition

명령어의 실행 순서에 따라 결과가 달라지는 상황을 race condition(경쟁 조건)이라고 한다. 스케쥴링에 의해 명령어의 순서가 엉키면 실행할때 마다 다른 결과가 나오게 되버린다.

2️⃣ Critical Section

위의 race condition이 발생하는 코드부분을 critical section(임계 영역)이라고 한다.

3️⃣ Mutual exclusion

위와 같은 상황들을 피하려면 쓰레드는 mutual exclusion(상호 배제) 라는 기법을 사용하여 하나의 쓰레드만이 임계 영역을 진입할 수 있도록 보장시켜야 한다.

4️⃣ Atomic

critical section을 atomic하게 만들어 상호 배제를 보장할 수 있다. 하지만 일반적으로 원자성을 보장하는 하드웨어로 모든 명령어를 한줄로 완성시킬 수는 없다. 그러면 어떻게 하나의 쓰레드만 임계영역에 진입하게 멀티 쓰레드 프로그래밍을 할 수 있을까? 운영체제에서 제공하는 대표적인 두가지 Synchronization primitives(동기화 함수)를 알아보자.

🔗 Mutex

POSIX 쓰레드 라이브러리가 제공하는 mutex락은 임계영역에 대한 상호 배제 기법이다.

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&lock);
counter = counter + 1;  //critical section
pthread_mutext_unlock(&lock);

trylock은 이미 락이 사용중이면 에러를 반환한다. timedlock은 타임아웃이 끝나거나 락을 획득해야 리턴된다.

pthread_mutex_trylock(pthread_mutex_t *m);
pthread_mutext_timedlock(pthread_mutext_t *m, struct timespec *abs_timeout);

🔗 Condition Variables

condition variable은 mutex에서 더 추가되어 어떤 상태가 다른 cpu에 의해 바뀌었다는걸 알았을때 다른 쓰레드가 나에게 시그너를 보내 깨워주게 할 수 있다.

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_mutex_lock(&lock);
while(ready == 0)
    pthread_cond_wait(&cond, &lock);    //대기상태
pthread_mutex_unlock(&lock);
pthread_mutex_lock(&lock);
ready = 1;
pthread_cond_signal(&cond); //위에껄 ready state로 바꿔줌
pthread_mutex_unlock(&lock);

유의할점 두가지가 있다. 첫째는 시그널을 보내고 ready를 수정할 때 반드시 lock을 가지고 있어야 한다는 점이다. 이를 통해 경재조건을 보장하게 된다. 둘째는 시그널 대기함수에서는 락을 두번째 인자로 받고있지만 시그널을 보내는 함수에서는 cond만 인자로 받는다. 이유는 시그널 대기함수는 쓰레드를 재우는 것 외에도 락을 반납해야 하기 때문이다.