Study/TIL(Today I Learned)

24.03.21 운영체제

에린_1 2024. 3. 21. 17:33
728x90

운영체제

23. 컨디션 변수

  • 쓰레드가 실행을 계속하기 전에, 특정 조건의 만족 여부를 검사해야 하는 경우가 있다. 그런 대기문은 어떻게 구현할까? 쓰레드가 특정 조건이 참이 될때까지 잠자면서 기다리는 방법이 좋다.

23.1 컨디션 변수의 개념과 관련 루틴

  • 쓰레드 실행시, 특정 조건이 만족될 때까지의 대기를 위해 컨디션 변수(Conditional variable)라고 불리는 개념을 사용할 수 있다. 컨디션 변수는 일종의 큐 자료 구조이다. 컨디션 변수는 쓰레드 실행에서 어떤 상태(또는 어떤 조건)가 원하는 것과 다를 때 조건이 만족되기를 대기하는 큐이다. 다른 쓰레드가 실행되어 시스템의 상태를 변경시키고, 해당 조건이 만족되었을 때, 대기중인 쓰레드(하나 이상의 쓰레드가 깨어날 수도 있다.) 를 깨워 실행을 계속토록 한다.
  • 컨디션 변수에는 wait()와 signal()이라는 두 개의 연산이 있다. wait()는 쓰레드가 스스로를 잠 재우기 위해서 호출하는 것이고, signal()은 조건이 만족되기를 대기하며 잠자고 있던 쓰레드를 깨울 때 호출한다.
  • wait()는 mutex를 인자로 받는다. wait()가 호출될 때 mutex는 잠겨있다고 가정한다. wait는 mutex를 해제하고 호출한 쓰레드를 재운다. 다른 쓰레드가 시그널을 보내어 대기중인 쓰레드가 슬립 상태에서 깨어나면, wait()에서 리턴하기 전에 반드시 락을 재획득 해야 한다. 시그널을 받아서 대기 상태에서 깨어났더라도 대기 모드에서 깨어난 프로세스가 자신이 반납했었던 락을 획득하지 않고 실행을 계속한다면 다양한 경쟁조건이 발생한다. 이를 방지하기 위해여 대기전과 후에 각각 락의 반납과 획득을 강제한다.
  • 상태변수done. 다른 쓰레드들이 알고자 하는 정보를 기록하는 done이라는 변수가 꼭 필요하다. 잠자고, 깨우고, ㄱ락을 설정하는 것이 done이라는 상태 변수 중심으로 구현되어있다.

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

  • 다수의 생산자 쓰레드와 소비자 쓰레드가 있다고 하자. 생산자는 데이터를 만들어 버퍼에 넣고, 소비자는 버퍼에서 데이터를 꺼내서 사용한다.
  • 유한 버퍼는 공유자원이다. 경쟁조건의 발생을 방지하기 위해 동기화가 필요하다.
  • 먼저 생산자는 넣고, 소비자는 꺼내어 쓸 수 있는 공유 버퍼가 하나 필요하다. 한 개의 정수를 사용하고(물론, 이곳에 다른 구조체를 가리키는 포인터를 넣을 수도 있다.) 공유 버퍼에 값을 넣는 루틴과 공유버퍼에 값을 꺼내는 루틴 두 개가 있다.
  • 버퍼에 데이터를 넣거나 버퍼에 데이터를 꺼내도 괜찮은지를 판단하는 루틴을 작성해야 한다. 버퍼가 비어있다면 데이터를 넣고, 버퍼가 가득 찼을때만 버퍼에서 데이터를 꺼낸다.

불완전한 해답

  • 생상자와 소비자 사이의 시그널을 보내는 과정을 살펴보자 생산자는 버퍼가 빌 때까지 기다린다. 소비자는 버퍼에 내용이 기록될 때를 기다린다. 생산자와 소비자가 각 하나씩인 경우에 코드는 제대로 동작한다. 하지만, 같은 종류의 쓰레드가 두 개 이상 있다면, 이런 해법에는 문제가 있다.
  • 시그널은 쓰레드를 깨우는 역할을 한다. 깨어난 스레드가 실행되는 시점에서는 버퍼의 상태가 다시 변경될 수도 있다. 시그널을 받았다는 것은, 해당 쓰레드가 실행될 때, 버퍼의 상태가 원하는 상태에 있을수도 있다는 일종의 힌트로 보아야한다. 때문에, 깨어난 쓰레드가 실제 실행되는 시점에는 시그널을 받았던 시점의 상태가 그대로 유지되어있는지를 다시 체크해야 한다.

개선된, 하지만 아직도 불완전한 if문 대신 while 문

  • 위 문제점은 쉽게 해결할 수 있다. if문을 while문으로 바꾸면 된다.
  • mesa semantic의 컨디션 변수에서 가장 기본적인 법칙은 언제나 while문을 사용하라는 것이다.

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

  • 이 문제에 대한 해법 역시 간단하다. 두 개의 컨디션 변수를 사용하면 된다. 시스템의 상태가 변경되었을 때 깨워야 하는 쓰레드에게만 시그널을 제대로 전달한다.
  • 생산자 쓰레드가 empty 조건변수에게 대기하고 fill에 대해서 시그널을 발생한다. 정반대로 소비자 쓰레드는 fill에 대해서 대기하고 empty에 대해서 시그널을 발생시킨다. 소비자가 실수로 다른 소비자를 절대로 깨울 수 없도록 하였고, 생산자도 다른 생산자를 깨우는 일이 절대 없도록 만들었다.

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

  • 버퍼를 확장하여 대기 상태에 들어가기 전에 여러 값들이 생산될 수 있도록 하는 것이다. 그리고 마찬가지로 여러 개의 값이 대기 상태전에 소비될 수 있도록 하는 것이다. 하나의 생선자와 소비자의 경우에서는 버퍼 크기가 증가하면 쓰레드간의 문맥교환이 줄어들기 때문에 더 효율적이 된다. 멀티 생산자의 경우 또는 멀티 소비자의 경우(또는 둘 다 인 경우)가 되면 생산과 소비가 병행이 될 수 있기 때문에 병행성이 좋아진다.

23.3 포함조건(covering condition)

  • 메모리 할당을 요청한 쓰레드는 메모리 공간이 생길 때까지 대기한다. 응용 프로그램이 메모리를 해제하고 반납하면 가용 메모리 공간의 발생을 알리는 시그널을 생성한다. 다수의 쓰레드가 메모리 공간의 발생을 대기하고 있는 경우, 어떤 쓰레드를 깨워야 하는가? 시그널을 생성하는 쓰레드는 시그널 수신 대상자를 명시할 수 없다.
  • 컨디션 변수에서의 시그널 함수는 시그널 수신자를 명시하지 않는다.
  • 이 문제에 대해서 해답은 컨디션 변수에서 대기중인 모든 쓰레드에게 시그널을 다 전송하는 것이다. 컨디션 변수를 대기중인 모든 스레드는 시그널을 받은 후, 대기 상태에서 준비상태로 전이된다. 대기 상태에 있었던 모든 쓰레드들이 깨어나서 실행된다. 깨어난 쓰레드들은 차례로 실행되면서, 자신이 대기했었던 조건이 만족되었는지를 검사한다. 조건이 만족되지 않았다면 다시 대기모드로 들어간다. 만약 조건이 만족되었다면, 해당 쓰레드는 wait()에서 리턴하여 실행을 계속한다. 어떤 쓰레드가 wait()에서 리턴하여 실행 할 경우, 해당 쓰레드는 락을 획득한 상태가 된다. 나머지 쓰레드들은 wait()에서 여전히 대기해야 한다. 즉, wait()에서 리턴하려면 컨디션 변수의 상태조건을 만족해야 하고, 락을 획득 해야 한다. 이 방법은 불 필요한 문맥전환이 많이 발생한다.
728x90

'Study > TIL(Today I Learned)' 카테고리의 다른 글

24.03.23 운영체제  (0) 2024.03.24
24.03.22 운영체제, KEYWORD  (1) 2024.03.23
24.03.20 PintOS  (0) 2024.03.21
24.03.19 퀴즈, 운영체제, PintOS  (1) 2024.03.19
24.03.18 운영체제, KEYWORD, PintOS  (1) 2024.03.19