Mutex Gotchas

So pthread_mutex_lock stops the other threads when they read the same variable?

아닙니다. mutex는 그렇게 똑똑하지는 않습니다. mutex는 code(thread)에 따라 동작하지, data에 따라 동작하지 않습니다. 오직 이미 lock이 되어있는 mutex에 다른 스레드가 lock을 호출 했을 때 이 스레드는 기존 스레드가 unlock될때까지 기다립니다.
int a;
pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER,
                m2 = PTHREAD_MUTEX_INITIALIZER;
// later
// Thread 1
pthread_mutex_lock(&m1);
a++;
pthread_mutex_unlock(&m1);

// Thread 2
pthread_mutex_lock(&m2);
a++;
pthread_mutex_unlock(&m2);

위와 같은 경우는 서로 다른 mutex를 사용하므로 여전히 race condition에 빠져있습니다.


Can I create mutex before fork-ing?

물론 가능하지만 child와 parent process는 가상 메모리를 공유하지 않고 각각은 서로 다른 mutex를 독립적으로 갖습니다.

(고급 옵션으로 공유된 메모리를 사용하면 child와 parent가 mutex를 공유할 수 있습니다. 적절한 옵션으로 공유 메모리 segment를 사용하면 child와 parent의 mutex를 공통으로 사용할 수 있습니다.)


If one thread locks a mutex can another thread unlock it?

아닙니다. 한 스레드에서 걸린 mutex lock은 그 스레드에서만 unlock이 가능합니다.


Can I use two or more mutex locks?

물론 가능합니다. 사실 일반적으로 업데이트 해야하는 각각의 자료구조당 하나의 lock을 갖습니다.

만약 하나의 lock만 존재한다면 불필요하게 두 개의 스레드가 하나의 lock을 가지고 경쟁할 수 있습니다. 예를 들어 두 스레드가 다른 counter를 update하는데, 동일한 lock으로 사용할 필요가 없습니다.


그러나 단순히 많은 lock을 만드는것으로는 불충분합니다. CS에 대해서 추론하는 것이 중요합니다. 하나의 스레드가 두 자료구조가 업데이트 되고 있는 중에 읽어 불안정한 상태가 되도록 않도록 하는 것이 중요합니다.


Is there any overhead in calling lock and unlock?

pthread_mutex_lock과 __unlock을 호출하는 것은 작은 overhead가 존재합니다. 그러나 이 대가는 정확하게 작동하는 프로그램을 작성하기 위해 지불해야 하는 대가입니다!


Simplest complete example?

#include <stdio.h>
#include <pthread.h>

// Compile with -pthread
// Create a mutex this ready to be locked!
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

int sum = 0;

void *countgold(void *param) {
    int i;
    
    // Same thread that locks the mutex must unlock it
    // Critical section is just 'sum += 1'
    // However locking and unlocking a million times
    // has significant overhead in this simple answer
    
    pthread_mutex_lock(&m);

    // Other threads that call lock will have to wait until we call unlock

    for (i = 0; i < 10000000; i++) {
	sum += 1;
    }
    pthread_mutex_unlock(&m);
    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;
}


위 코드에서 스레드는 counting house에 진입하기 전에 lock을 얻습니다. CS는 정확히 sum +=1인 부분이므로 

    for (i = 0; i < 10000000; i++) {
        pthread_mutex_lock(&m);
        sum += 1;
        pthread_mutex_unlock(&m);
    }
    return NULL;
}


위와 같이 이 부분에만 lock을 얻을 수 있지만 이렇게 한다면 mutex의 lock과 unlock을 백만 번 반복하므로 속도가 더 느려집니다. 적어도 incrementing variable보다는 훨씬 overhead가 크기 때문입니다. (실제 이 예에서는 스레드도 필요 없습니다- 단지 2번 반복하면 끝나는 일이기 때문이죠)

더 빠른 multi-thread example은 아래와 같이 지역 변수에서 백만번을 더하고 나중에 lock을 건 후에 이 지역변수값을 sum에 더하는 것입니다.

   int local = 0;
    for (i = 0; i < 10000000; i++) {
       local += 1;
    }

    pthread_mutex_lock(&m);
    sum += local;
    pthread_mutex_unlock(&m);

    return NULL;
}



What happens if I forgot to unlock?

unlock을 까먹으면 deadlock에 걸립니다. 조금 뒤에 deadlock에 대해 언급하겠지만 아래와 같은 루프를 여러 스레드에서 호출할 때 발생하는 문제점은 무엇을까요??
while (not_stop) {
    // stdin may not be thread safe
    pthread_mutex_lock(&m);
    char *line = getline(...);
    if (rand() % 2) { /* randomly skip lines */
         continue;
    }
    pthread_mutex_unlock(&m);
    
    process_line(line);
}



When can I destroy the mutex?

mutex가 unlock되엇을 때만 destroy가 가능합니다.


Can I copy a pthread_mutex_t to a new memory location?

mutex의 메모리를 새로운 메모리로 복사해서 사용하는 것은 지원되지 않습니다.

What would a simple implementation of a mutex look like?

단순한(정확하지 않은) 제안은 다음과 같습니다.


// Version 1 (Incorrect!)

void lock(mutex_t *m) {
    while(m->locked) { /*Locked? Nevermind - just loop and check again!*/ }

    m->locked = 1;
}

void unlock(mutex_t *m) {
    m->locked = 0;
}


unlock function은 단순히 mutex를 unlock해 반환해줍니다. lock function은 일단 lock이 locked된 상태인제 확인한 후에 이미 locked되어 있는 상태라면 다른 스레드가 이 mutex를 unlock할 때까지 계속 체크합니다.


Version 1은 busy-waiting(불필요하게 CPU resource를 사용)을 사용하고 있고, 더욱 심각한 문제로는 race condition을 가지고 있다는 것입니다.


만약 두개의 스레드가 lock을 동시에 호출한다면 두 스레드 모두 m->locked를 0으로 읽을 수 있습니다. 그런 경우 두 스레드 모두 독점적인 접근이 가능하다고 믿고 두 스레드 모두 진행되게 됩니다.


loop안에서 pthread_yield()를 호출해서 CPU overhead를 줄이려는 시도를 할 수 있습니다.

pthread_yield는 OS에게 이 스레드가 잠시동안 CPU를 사용하지 않도록 제안하는 것입니다. 그렇게 되면 CPU는 동작하길 기다리는 다른 스레드에게 할당됩니다. 하지만 이것이 race-condition을 고쳐주지는 않습니다. race condition을 막을 더 좋은 구현이 필요합니다.

Posted by 몰랑&봉봉
,