티스토리 뷰

운영체제(Three Easy Pieces)

30. 컨디션 변수

ccc124213131 2021. 6. 13. 01:05
728x90

운영체제: 가장 쉬운 세가지 이야기 - Operating Systems: Three Easy Pieces를 바탕으로 작성하였습니다.


 

컨디션 변수

- 컨디션 변수는 특정 조건이 만족되기를 기다리며 대기하는 "큐"다.

- 다른 쓰레드가 시스템의 상태를 변경시키고, 이에 따라 조건이 충족되면 대기 중인 쓰레드에게 signal을 발생시킨다.

- 쓰레드 실행 전 특정 조건의 만족 여부를 검사하는 경우는 흔하다. 이 때 컨디션 변수를 사용할 수 있다.

- 회전 대기(while과 flag로 구현)는 단순하나 낭비가 있다.

 

동작

*어떤 쓰레드가 먼저 실행될지는 비결정적이라는 사실을 기억한다.

1. 부모 쓰레드가 먼저 실행되는 경우

1 thr_join() 실행, done이 1이 아니면 wait() 실행, 이 때 보유 중인 락을 해제하고 대기에 들어간다.

2 자식 쓰레드가 실행되고, child()를 실행한 후 exit()를 실행한다. 락 획득 후 done = 1로 할당하고 signal을 보낸 직후 락을 해제한다.

3 signal에 의해 대기 중인 쓰레드가 깨어남과 락을 획득하고, 락 해제 후에 종료한다.

 

2. 자식 쓰레드가 먼저 실행되는 경우

1 done = 1을 설정한다. signal을 보내도 깨울 쓰레드가 없다. 종료한다.

2 부모 쓰레드가 실행된다. done이 1이므로 while stmt 안으로 들어가지 않는다. 따라서 wait()을 실행하지 않고 종료한다.

 

생산자/소비자(유한 버퍼)문제

- 유한 버퍼의 흔한 예로, 멀티 쓰레드 웹 서버의 경우, 생산자 쓰레드가 다수의 HTTP 요청을 받아 작업 큐(유한 버퍼)에 저장한다. 이후 소비자 쓰레드가 이 큐에서 요청들을 꺼내 처리한다.

- 유한 버퍼는 "공유 자원"이다. 따라서 여러 쓰레드간 경쟁을 방지하기 위해 동기화가 필요하다.

- put(), get()과 함께 구현된다. 단순하다. 생산자 쓰레드가 put으로 버퍼에 데이터를 담고, 소비자 쓰레드가 get으로 데이터를 수거해 간다.

   - 생산자 쓰레드는 put()으로 버퍼에 데이터 넣기 전 버퍼가 비어있는지 확인한다.

   - 소비자 쓰레드는 get()으로 버퍼에서 데이터를 획득하기 전 버퍼가 차있는지 확인한다.

 

불완전한 해답

- 락과 컨디션 변수를 함께 사용한다.

- 생산자 쓰레드가 signal을 보내 소비자 쓰레드가 wait()에서 깨어나는(blocked -> ready) 시점과 이후 깨어난 쓰레드의 실행 시점에서의 버퍼의 상태가 다른 소비자 쓰레드의 실행으로 인해 달라질 수 있다.

아래의 방식으로 해결한다.

   - Mesa semantic:

      - if를 while로 바꾸면 해결된다. 쓰레드가 조건을 재검사하지 않기 때문에 발생하는 문제이기 때문이다.

      - 대부분의 시스템에서 사용된다.

   - Hoare semantic

- 또 다른 문제: 누가 어떤 쓰레드를 깨워야 할까?

   - 컨디션 변수가 하나이기 때문에 발생하는 문제

      - 소비자 쓰레드가 생산자 쓰레드를 깨워야하는데 signal 구현 특성 상 누구를 깨울지 지정할 수 없으므로, 다른 소비자 쓰레드를 깨울 수 도 있다. 이 경우 다른 쓰레드를 "깨운" 쓰레드는 대기에 들어가고, "깨워진" 쓰레드도 버퍼를 확인하고 비어있기 때문에 대기 상태에 들어간다. 모든 쓰레드가 다 sleep 상태에 들어가는 문제가 발생한다.

 

단일 버퍼 생산자/소비자 해법

- 두 개의 컨디션 변수를 사용한다.

- 소비자는 생산자, 생산자는 소비자에게만 signal()을 보낼 수 있게 된다.

 

올바른 생산자/소비자 해법

- 버퍼 크기를 늘린 버전이다.

- 단일 생산/소비자의 경우, 여러 개의 생산/소비자의 경우 모두 병행성이 증가한다.

 

포함 조건(Covering Condition)

- 예를 들어 다수의 쓰레드가 메모리 공간의 발생을 대기하고 있는 경우 어떤 쓰레드를 깨워야 하는가? 사실 어떤 쓰레드를 깨울지 지정하는 것도 불가능하다.

T_a가 100 bytes, T_b가 10 bytes를 요청하고 대기 중이라고 하자, T_c가 free()를 통해 50 bytes를 방출하면 T_b에게 signal이 가야하는 것이 맞나, 이것은 보장될 수 없다.

   -> 모두에게 signal 보내는 방식으로 해결한다.

           - 조건 만족하는 쓰레드만 락을 획득하게 된다.

           - 문제는 불필요한 문맥 전환 비용이 발생하게 된다는 점이다.

 

 

★ 멀티 쓰레드 프로그램에서 조건 검사 시 if가 아닌 while을 사용한다. signal 전달 과정에서 경쟁 조건에 의해 발생할 수 있는 spurious wakeup 문제에 대비하기 위해서다.

 

'운영체제(Three Easy Pieces)' 카테고리의 다른 글

29. 락 기반의 병행 자료구조  (0) 2021.06.13
29. 락  (0) 2021.06.13
댓글