Solving Critical Sections

What is a Critical Section?

critical section(이하 CS)은 함수가 정확히 동작하려면 한 번에 한 스레드에 의해서만 수행되어야 하는 코드 부분입니다.  만약 두개의 스레드가(또는 프로세스) CS안에 코드를 실행하는 경우 프로그램이 더이상 정확하지 않은 동작을 할 가능성이 존재합니다.

Is just incrementing a variable a critical section?

단순히 변수를 증가시키는 동작도 critical section입니다. 변수를 증가시키는 것(i++)은 내부적으로는 3개의 step으로 수행됩니다.
1. memory contents를 CPU register에 복사. 2.CPU 안에 값 증가. 3. 새로운 값을 메모리에 저장
만약 메모리 위치가 하나의 쓰레드에 의해서만 접근 가능하다면 (automatic variable i) race condition의 가능성은 없고 i와 관련된 CS도 없습니다. 하지만 sum variable은 global variable이고 두 개의 스레드에 의해 접근이 가능합니다. 두개의 스레드가 동시에 변수를 증가하려는 시도가 가능합니다.
#include <stdio.h>
#include <pthread.h>
// Compile with -pthread

int sum = 0; //shared

void *countgold(void *param) {
    int i; //local to each thread
    for (i = 0; i < 10000000; i++) {
        sum += 1;
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, countgold, NULL);
    pthread_create(&tid2, NULL, countgold, NULL);
    
    //Wait for both threads to finish:
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    printf("ARRRRG sum is %d\n", sum);
    return 0;
}


일반적으로 위 코드의 아웃풋은 ARGGGH sum is 8140268입니다. 하지만 실제로는 race condition 때문에 프로그램을 실행할 때마다다른 값이 나옵니다. code가 두 쓰레드가 sum 변수를 동시에 reading하거나 writing하는 것을 막지 않고 있기 때문입니다. 예를 들어 두 스레드가 현재 sum의 값을 cpu에 복사해 각각의 스레드를 동작한다고 생각해 봅시다. 둘 다 각자 복사한 내용에 1을 증가시킵니다. 두 스레드는 124를 주소에다가 쓰게 됩니다. 만약 두 스레드가 다른 시간에 sum에 접근했다면 125가 되었을 것입니다.


How do I ensure only one thread at a time can access a global variable?

당신은 지금 mutex가 필요합니다!!도와주세요!!라고 말하고 있는 것과 같습니다. 만약 한 스레드가 현재 CS에 진입했다면 이 스레드가 완료되기 전까지 다른 스레드가 기다리게 해야 합니다. 이러한 목적으로 mutex를 사용할 수 있습니다(mutex는 Mutual Exclusion의 약자입니다).

간단한 예는 아래와 같이 가장 적게 3개의 줄만을 더해 만들 수 있습니다.

pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; // global variable
pthread_mutex_lock(&m);   // start of Critical Section
pthread_mutex_unlock(&m); // end of Critical Section

일단 mutex를 사용해 하는 작업이 끝났다면 pthread_mutex_destroy(&m)을 호출해줘야 합니다. 오로지 unlock된 mutex만 destroy하는 것이 가능하다는 것을 기억해 주세요.  destroy lock을 다시 destroy하는 것, initialized lock을 다시 initializing하는 것, 이미 lock된 lock을 다시 locking하는 것, unlocked lock을 다시 unlock하는 것 등등 모두 지원되지 않고(적어도 기본 mutex에서는) 보통 정의되지 않은 행동을 일으킵니다.


If I lock a mutex, does it stop all other threads?

아닙니다. 다른 스레드들은 계속 동작합니다. 오직 스레드가 locked mutex에 다시 lock을 하기 위해 시도할 때 그 스레드는 기다리게 됩니다. 기존에 CS에 진입했던 스레드가 mutex를 unlock하면 그 전에 기다리고 있던 스레드가 lock을 얻고 계속 진행되게 됩니다.

Are there other ways to create a mutex?

있습니다. macro로 PTHREAD_MUTEX_INITIALIZER 을 사용해 global(static) 변수로 사용합니다. 
m = PTHREAD_MUTEX_INITIALIZER는 pthread_mutex_init(&m, NULL)과 동일합니다. init version에는 성능은 떨어지지만 추가적인 error check와 고급 공유 옵션을 사용할 수 있는 옵션이 포함합니다.

pthread_mutex_t *lock = malloc(sizeof(pthread_mutex_t)); 
pthread_mutex_init(lock, NULL);
//later
pthread_mutex_destroy(lock);
free(lock);


다음은 init과 destroy에 대해 항상 명심해야 할 점들입니다.


  • 여러개의 스레드를 init/destroy하는 것은 정의되지 않은 동작을 일으킬 수 있습니다.
  • locked mutex를 destroy하는 것은 정의되지 않은 동작을 일으킬 수 있습니다.
  • 기본적으로는 하나의 스레드가 mutex를 초기화하고 동작하는 패턴을 유지하는게 좋습니다.






Posted by 몰랑&봉봉
,