3 분 소요

Lock + Condition Variables = Semaphore

semaphore 는 lock 과 condition variables 를 모두 기능할 수 있습니다.

int sem_init(sem_t *s, int pshared, unsigned int value)

  • 만약 pshared 가 0 이라면, semaphore 는 프로세스 내의 스레드들 간에 공유됩니다.
  • 그렇지 않다면, semaphore 는 프로세스들 간에 공유됩니다. 또한 공유 메모리에 존재해야 합니다.
  • unsigned int value : Lock 혹은 Condition Variable

int sem_wait(sem_t *s)

  • semaphore 의 value 값을 1 감소시킵니다.
  • 만약 semaphore 의 value 가 음수가 된다면 sleeping 상태가 됩니다.

int sem_post(sem_t *s)

  • semaphore 의 value 값을 1 증가시킵니다.
  • 만약 waiting 중인 스레드들이 있다면 하나를 깨웁니다.

Senaphores for Ordering

sem_t s;
void * child(void *arg) {
    printf("child\n");
    sem_post(&s);
    return NULL;
}
int main(int argc, char *argv[]) {
    pthread_t c;
    sem_init(&s, 0, X); // what should X be?
    printf("parent: begin\n");
    pthread_create(&c, NULL, child, NULL);
    sem_wait(&s);
    printf("parent: end\n");
    return 0;
}

한가지 예외사항을 고려한다면, wait이 불러졌을 때 sleeping이 되어야 합니다. wait이 불러지기 전에 post가 호출되면 value +=1 이 됩니다 이후에 wait이 불러졌을 때 value -=1 값이 음수가 된다면 sleeping 됩니다. 이때, post가 이미 호출됐으므로 해당 thread 가 계속 잠들게 됩니다. 결론적으로, 모든 상황을 고려할 때, semaphore 의 초기 value 는 0이 되어야 합니다.

Semaphores in Producer/Consumer Problem

buffer 의 state 정보를 가리키는 count 변수가 필요하지 않습니다. semaphore 변수 empty 와 full 을 선언합니다. 이 semaphore 변수들이 결국 state 정보를 내포하며 count 변수의 역할을 수행합니다.

  • empty -=1 음수가 아니라면 put(i) 후에 full +=1
  • full 변수가 양수인경우 get() 후에 empty +=1
    하지만 mutiple producer and consumer 인 경우 race condition 이 발생합니다. 그 해결책으로 mutex 라는 semaphore 변수를 1로 초기화 하여 한개 더 선언한 뒤 한번 더 감싸게 됩니다. 이는 실제 mutex lock 은 아니지만 같은 기능을 하게 됩니다. Race condition 은 이렇게 해결했지만 또 다른 문제가 생기는데, 그 문제가 Deadlock 문제 입니다.

DeadLock

void *producer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&empty);
        sem_wait(&mutex);
        put(i);
        sem_post(&mutex);
        sem_post(&full);    
    }
}
void *consumer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&full);
        sem_wait(&mutex);
        int tmp = get();
        sem_post(&mutex);
        sem_post(&empty);
    }
}

여러 thread 가 있을 때, 한 thread 가 현재 기다리는 상태를 해결해 줄 수 있는 thread 가 다른 thread라면 서로를 기다리기만 하게 되고 멈춰있는 상태 입니다.

과정

consumer -> mutex semaphore -=1 == 0 -> wait -> full -=1 -> sleeping -> producer -> mutex sema -=1 == -1 -> sleeping -> 이를 해결해 줄 수 있는건 consumer의 sem_post -> 하지만 post 가 불리기 위해선 full 이 증가해야함 -> full 은 producer 가 put 해줘야함 -> 하지만 잠들어있음.. -> Deadlock (교착상태) 발생

Solution

mutex semaphore 를 empty 와 full semaphore 안쪽으로 배치시키는 것 입니다.

Reader-Writer Locks

Reader 의 lock 함수 Writer 의 lock 함수

typedef struct _rwlock_t {
    // binary semaphore (basic lock)
    sem_t lock;
    // used to allow ONE writer or MANY readers
    sem_t writelock;
    // count of readers reading in critical section
    int readers;
} rwlock_t;

void rwlock_init(rwlock_t *rw) {
    rw->readers = 0;
    sem_init(&rw->lock, 0, 1);
    sem_init(&rw->writelock, 0, 1);
}

void rwlock_acquire_writelock(rwlock_t *rw) {
    sem_wait(&rw->writelock);
}
void rwlock_release_writelock(rwlock_t *rw) {
    sem_post(&rw->writelock);
}

void rwlock_acquire_readlock(rwlock_t *rw) {
    sem_wait(&rw->lock);
    rw->readers++;  
    if (rw->readers == 1)
        // first reader acquires writelock
        sem_wait(&rw->writelock);
    sem_post(&rw->lock);
}
void rwlock_release_readlock(rwlock_t *rw) {
    sem_wait(&rw->lock);
    rw->readers--;
    if (rw->readers == 0)
        // last reader releases writelock
        sem_post(&rw->writelock);
    sem_post(&rw->lock);
}

장점

공유되는 자료구조에 대해 여러개의 readers 가 동시에 접근해 race condition 없이 concurrency 를 늘려줄 수 있습니다. writer 끼리는 writerlock에 의해 동기화되므로 race condition이 발생하지 않습니다.

문제점

하지만 writer가 reader에 비해 우선순위가 밀리는 문제점이 발생할 수 있습니다. 예를들어 reader가 계속해서 진입한다면 중간에 writer가 작업이 필요해도 reader 가 0이 될때까지 writer 가 starvation 하게 됩니다.

How to implement Semaphore (CODE)

typedef struct __Sem_t {
    int value;
    pthread_cond_t cond;
    pthread_mutex_t lock;
} Sem_t;
// only one thread can call this
void Sem_init(Sem_t *s, int value) {
    s->value = value;
    Cond_init(&s->cond);
    Mutex_init(&s->lock);
}

void Sem_wait(Sem_t *s) {
    Mutex_lock(&s->lock);
    while (s->value <= 0)
        Cond_wait(&s->cond, &s->lock);
    s->value--;
    Mutex_unlock(&s->lock);
}
void Sem_post(Sem_t *s) {
    Mutex_lock(&s->lock);
    s->value++;
    Cond_signal(&s->cond);
    Mutex_unlock(&s->lock);
}

업데이트: