본문 바로가기
공부/OS

[운영체제] 병행성 3 (Concurrency) : Condition Variable

by algosketch 2021. 5. 31.

 lock 만으로는 어떤 조건을 만족해야 수행할 수 있는 스레드를 구현할 수 없다. 락과 함께 어떤 조건을 만족하는 지 확인하는 데, 그 조건, 스레드의 수행을 제어하는데 사용하는 것이 condition variable 이다. condition variable 은 락과 함께 사용된다.

 condition variable : 스레드들이 공유자원을 향해서 경재하고 있을 때 스레드 수행을 제어하기 위해 사용하는 일종의 시그널이다.

 bouned buffer

만약에 어떤 buffer 가 있고 이 버퍼의 크기가 특정 이상이 되면 그 때 읽는 로직을 작성하고자 한다면 어떻게 구현해야 하겠는가? 버퍼의 크기가 특정 크기가 될 때까지 스레드는 기다린다. 조건을 만족하게 되면 시그널로 알려 준다. 처음에 버퍼를 읽어들일 때 조건을 만족하지 못 하면 wait 로 들어가게 되는데 이 wait 는 park 와는 다르다. lock 을 가진 상태에서 wait 를 호출하는 것이기 때문에, wait 를 부르면 자동으로 unlock 되어야 한다. wait 가 어떤 식으로 구현되어 있는지는 나중에 pthread 에서 다룬다고 하셨는데, 아무래도 이 내용은 안 하고 학기가 끝날 것 같다. 특정 조건을 만족하면 condition variable 을 통해 signal 을 보내고, wait 상태로 들어간 스레드는 이 시그널을 받고 깨어 난다.

producer : 데이터를 생성
consumer : 데이터를 사용

 producer 는 버퍼가 꽉 차면 더 이상 데이터를 작성할 수 없고 consumer 는 데이터가 비어있을 때 읽을 수 없다. 예로 grep foo file.txt | wc -l 가 있다.

int buffer;
int count = 0;

void put(int value) {
    count = 1;
    buffer = value;
}

void get() {
    count = 0;
    return buffer;
}

 위 코드는 buffer 의 크기가 1인 간단한 예제이다. put 과 get, condition variable 을 사용할 때 lock 을 해야 한다. count 의 값이 1이면 pthread_cond_wait(&cond, &mutex) 를 호출한다. 두 개의 인자를 넘겨주는 것은 cond 라는 condition variable 에 대한 시그널을 받고 깨겠다는 의미, mutex 를 unlock 하겠다는 의미인 듯하다. producer 는 lock -> put 이후에 signal 을 보내준다. (기다리고 있는 consumer 가 있을 수 있기에) consumer 도 마찬가지로 구현한다. mutex 를 갖고 있는 상태에서 IO(printf)를 하게 되면 block 될 수 있다. 이렇게 구현한 코드는 consumer 와 producer 가 하나일 때를 제외하고는 제대로 동작하지 않는다.
 consumer 1 이 먼저 동작하면 읽을 게 없기 때문에 sleep 상태가 된다. 그러고 producer 가 buffer 에 두 번 쓰게 되면 두 번째 작성할 때 sleep 이 걸린다. 이 다음에 consumer 1 이 아닌 consumer 2 가 버퍼를 읽은 후 consumer 1 이 실행되게 되면 consumer 1 은 (signal 을 받고 깨면 무조건 읽을 게 있다고 가정한 코드에서는)읽을 것이 없다. producer 나 consumer 가 깨어났을 때 원하는 상태인지 확인을 안 했다.

Mesa semantics : 깨어났을 때 원하는 상태임이 보장되지 않는 경우
Hoare semantics : 깨어났을 때 원하는 상태임이 보장되는 경우

 일반적인 경우 대부분 Mesa sematic 이다. 기존에 if (count == 1) 로 구현했다면 while (count == 1) 로 바꾸면 된다. 그런데 사실 이것도 정확한 코드는 아니다. consumer 1 -> consumer 2 -> producer -> consumer 1 -> consumer 2 순서로 수행된다고 하면 마지막에 consumer 2 가 sleep 되는 시점에서 모든 스레드가 sleep 상태에 빠지게 된다. 어떤 스레드를 깨울 것인가를 제대로 정의하지 않았기 때문에 이런 문제가 발생한다.
 따라서 producer 와 consumer 는 서로 다른 조건을 가져야 한다.

cond_t empty, fill;

 객체지향으로 condition variable 을 하나로 묶을 수도 있는데, 이를 monitor 라고 부른다. 이것을 명시적으로 제공해주는 경우는 없고 직접 구현해야 한다. java 같은 경우에는 synchronized 키워드를 이용할 수 있다.