12.1 동기화란

  • 협력하여 실행되는 프로세스들은 실행 순서자원의 일관성을 보장하기 위해 동기화(synchronization) 되어야 한다

12.1.1 동기화의 의미

  • 프로세스 동기화: 프로세스들 사이의 수행 시기를 맞추는 것
    • 실행 순서 제어: 프로세스를 올바른 순서대로 실행하기
    • 상호 배제(mutual exclusion): 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
  • 프로세스뿐만 아니라 스레드도 동기화 대상
  • 정확히 말하면 실행의 흐름을 갖는 모든 것은 동기화의 대상
  • 다만 이 책에서는 대부분의 전공서 표현에 따라 ‘프로세스 동기화’라고 칭함
  • 실행 순서 제어를 위한 동기화

    image-20240501125906938.png

    • Reader 프로세스는 Writer 프로세스 실행이 끝나야 비로소 실행할 수 있어야 한다
    • Writer 가 Book.txt에 값을 저장하기도 전에 Reader 가 Book.txt를 읽는 것은 올바른 실행 순서가 아니다.
    • Reader 는 ‘Book.txt 안에 값이 존재한다’는 특정 조건이 만족되어야만 실행을 이어나갈수 있다
    • 프로세스를 올바른 순서대로 실행하는 것
  • 상호 배제(mutual exclusion)를 위한 동기화

    • 동시에 접근해서는 안 되는 자원에 동시에 접근하지 못하게 하는 것

    • 두 개 이상의 프로세스가 임계 구역동시에 접근하지 못하도록 관리하는 것

      image-20240501130315938.png

    • 프로세스 A와 B가 동시에 실행되었다고 가정

    • 17만원이 계좌에 남을 거라고 유저는 기대

    • A와 B는 ‘잔액’이라는 데이터를 동시에 사용하는데, A가 끝나기도 전에 B가 잔액을 읽어 버렸기 때문에 엉뚱한 결과가 나옴

    • A와 B를 올바르게 실행하기 위해서는 한 프로세스가 잔액에 접근했을 때 다른 프로세스는 기다려야 한다

SCR-20241101-pykt.png

image-20240501130117792.png

12.1.2 생산자와 소비자 문제

SCR-20240501-ltaj.png

  • 상호 배제를 위한 동기화에 관련된 고전적이고 유명한 문제로 생산자와 소비자 문제가 있다.

    • 물건을 계속해서 생산하는 프로세스인 생산자

    • 물건을 계속해서 소비하는 프로세스인 소비자

    • 생산자와 소비자는 동시에 실행되는 스레드가 될 수도 있다.

image-20240501130925718.png

  • 생산자를 100,000번, 소비자를 100,000번 동시에 실행

    • 유저는 총합 변수가 계속 10개로 머물러 있을 것으로 기대

    • 하지만 결과는 다름: 생산자 프로세스와 소비자 프로세스가 제대로 동기화되지 않았기 때문에 발생한 문제

    • 생산자와 소비자는 ‘총합’이라는 데이터를 동시에 사용하는데, 생산자가 소비자의 작업이 끝나기도 전에 총합을 수정해서때문에 엉뚱한 결과가 발생

    • 한 마디로 동시에 접근해서는 안 되는 자원에 동시에 접근했기에 발생한 문제

12.1.3 공유 자원과 임계 구역

  • 계좌 잔액 문제와 생산자 소비자 문제의 동시에 실행되는 프로세스들은 전역 변수 ‘잔액’, ‘총합’이라는 공동의 자원을 두고 작업을 했습니다.

    • 이러한 자원을 공유 자원; shared resource 이라 한다.

    • 공유 자원은 전역 변수, 파일, 입출력장치, 보조기억장치가 될 수도 있다.

  • 임계 구역; critical section: 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역

    • 임계 구역은 두 개 이상의 프로세스가 동시에 실행되면 안 되는 영역
    • 두 개이 상의 프로세스가 임계 구역에 진입하고자 하면 둘 중 한는 대기해야 한다.

    image-20240501131224331.png

  • 경쟁 상태; Race condition: 여러 프로세스가 동시 다발적으로 ‘‘임계 구역’‘의 코드를 실행하여 문제가 발생하는 경우

    • 데이터 일관성이 깨지는 문제 발생
    • image-20240501131524896.png
    • 컴퓨터는 고급 언어가 아닌 저급 언어를 실행하기 때문에 여러 줄의 저급 언어로 변환된 고급 언어한 줄을 실행하는 과정에서 문맥 교환이 일어남.
    • 저급 언어를 실행하는 과정에서 문맥교환이 일어난다면 아래와 같은 문제가 발생
    • image-20240501131707202.png
  • ‘상호 배제를 위한 동기화’ 를 위해서는 세 가지 원칙이 반드시 지켜져야만 한다.

    1. 상호 배제; mutual exclusion: 한 프로세스가 임계 구역에 진입했다면, 다른 프로세스는 임계 구역에 들어올 수 없다.
    2. 진행; progress: 임계 구역에 어떤 프로세스도 진입하지 않았다면, 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
    3. 유한 대기; bounded waiting: 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다 (임계 구역에 들어오기 위해 무한정 대기해서는 안 된다).

12.2 동기화 기법

12.2.1 뮤텍스 락

image-20240501132256831.png

  • 옷 가게의 탈의실을 이용하는 것에 비유

  • 손님-프로세스, 탈의실-임계 구역

  • 자물쇠가 걸려 있지 않다면 탈의실을 이용하면 된다, 걸려 있다면 대기

  • 뮤텍스 락; MUTual EXclusion Lock: 이 자물쇠 기능을 코드로 구현한 것

    • 상호 배제를 위한 동기화 도구

    • 임계 구역에 진입하는 프로세스는 ‘내가 지금 임계 구역에 있음’을 알리기 위해 뮤텍스 락을 이용해 임계 구역에 자물쇠를 걸어둘 수 있고

    • 다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨 있지 않다면 임계 구역에 진입할 수 있다.

  • 뮤텍스 락의 단순한 형태는 하나의 전역 변수와 두 개의 함수로 구현 가능

    • 자물쇠 역할: 프로세스들이 공유하는 전역 변수 lock
    • 임계 구역을 잠그는 역할: acquire 함수
    • 임계 구역의 잠금을 해제하는 역할: release 함수

image-20240501132954873.png

  • acquire 함수는 프로세스가 임계 구역에 진입하기 전에 호출하는 함수

    • 만일 임계 구역이 잠겨 있다면 임계 구역이 열릴 때까지 (lock이 false가 될 때까지) 임계 구역을 반복적으로 확인하고,
      • 마치 탈의실 문이 잠겨 있는지 쉴 새 없이 반복하며 확인해 보는 것과 같다.
      • 이런 대기 방식을 바쁜 대기; busy wait 이라 한다.
    • 임계 구역이 열려 있다면 임계 구역을 잠그는 (lock을 true로 바꾸는) 함수
  • release 함수는 임계 구역에서의 작업이 끝나고 호출하는 함수

    • 현재 잠긴 임계 구역을 열어주는 (lock을 false로 바꾸는) 함수
  • 이렇게 되면 프로세스는

    • 락을 획득할 수 없다면 (임계 구역에 진입할 수 없다면) 무작정 기다리고,
    • 락을 획득할 수 있다면 (임계 구역에 진입할 수 있다면) 임계 구역을 잠근 뒤 임계 구역에서의 작업을 진행하고,
    • 임계 구역에서 빠져나올 때엔 다시 임계 구역의 잠금을 해제함으로써 임계 구역을 보호할 수 있다.

12.2.2 세마포

image-20240501133410400.png

  • 세마포; semaphore뮤텍스 락과 비슷하지만, 조금 더 일반화된 방식의 동기화 도구

    • 엄밀히 말하면 세마포의 종류에도 이진 세마포(binary)와 카운팅 세마포(counting)가 존재
    • 이진 세마포는 뮤텍스 락과 비슷한 개념이므로 여기서는 여러 공유 자원을 다룰 수 있는 카운팅 세마포를 다룬다
  • 탈의실이 여러 개 있는 상황처럼 공유 자원이 여러 개 있을 경우 (각 공유 자원에는 하나의 프로세스만 진입이 가능할지라도) 여러 개의 프로세스가 각각 공유 자원에 접근이 가능해야 한다.

    • 한 번에 하나의 프로세스만 이용할 수 있는 프린터 세 대가 있는 상황에서
    • 하나의 프린터를 사용할 수 있는 프로세스는 하나이지만, 총 세 개의 프로세스공유 자원 (세 대의 프린터) 을 이용할 수 있다.
  • 세마포는 공유 자원이 여러 개 있는 상황에서도 적용이 가능한 동기화 도구

  • 세마포는 뮤텍스 락과 비슷하게 하나의 변수와 두 개의 함수로 단순하게 구현할 수 있다.

    • 전역변수 S: 임계 구역에 진입할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수)
    • wait 함수: 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는 함수
    • signal 함수: 임계 구역 앞에서 기다리는 프로세스에 ‘이제 가도 좋다’고 신호를 주는 함수
  • 여기에서 세마포 변수와 함수 이름을 각각 S, wait, signal로 나타내지만, 변수, 함수 이름은 전공서마다 다를 수 있다.
  • 일부에서는 세마포를 처음 개발한 다익스트라에 근거해 wait와 signal 함수를 P, V로 명명
  • 일부에서는 철도 신호기에 근거해 down, up으로 명명
  • 다만 변수와 함수를 어떻게 지칭하든지 원리와 작동 방식은 모두 동일

image-20240501134031092.png

  • wait 함수

    • image-20240501134119061.png
    • 만일 임계 구역에 진입할 수 있는 프로세스 개수가 0 이하라면
    • 사용할 수 있는 자원이 있는지 반복적으로 확인하고,
    • 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1 감소시키고 임계 구역 진입한다.
  • signal 함수

    • image-20240501134131711.png
    • 임계 구역에서의 작업을 마친 뒤 S를 1 증가

image-20240501134259521.png

  • 여기서 한 가지 문제가 있습니다.

    • 이는 뮤텍스 락에도 해당되는 문제
    • 사용할 수 있는 공유 자원이 없는 경우 프로세스는 무작정 무한히 반복하며 전역 변수 S를 확인해야 합니다.
    • 이렇게 바쁜 대기(busy wait) 를 반복하며 확인할 시간에 CPU는 더 생산성 있는 작업을 할 수 있을 텐데, CPU 주기를 낭비한다는 점에서 손해
  • 그래서 실제로 세마포는 다른 더 좋은 방법을 사용

    • wait 는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태대기 상태로 만들고,
    • 그 프로세스의 PCB를 세마포를 위한 대기 큐집어 넣음
    • 다른 프로세스가 임계 구역에서의 작업이 끝나고 signal 를 호출하면 signal 는 대기 중인 프로세스를 대기 큐에서 제거하고,
    • 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨줌.

image-20240501134544777.png

image-20240501134843825.png

  • 지금까지는 상호 배제를 위한 동기화 기법
  • 아래는, 세마포를 이용해 실행 순서 제어를 위한 동기화(프로세스의 순서를 제어하는 방법) 에 대해 설명
  • 세마포를 이용하면 동시에 실행되는 프로세스의 실행 순서도 원하는 대로 제어할 수 있다.
    • 세마포의 변수 S를 0으로 두고 먼저 실행할 프로세스 뒤에 signal 함수, 다음에 실행할 프로세스 앞에 wait 함수를 붙이면 됩니다.
    • image-20240501135146569.png
    • P1이 먼저 실행되면 P1이 임계 구역에 먼저 진입하는 것은 자명
    • P2가 먼저 실행 되더라도 P2는 wait 함수를 만나므로 P1이 임계 구역에 진입
    • 그리고 P1이 임계 구역의 실행을 끝내고 signal을 호출하면 그제서야 P2가 임계 구역에 진입
    • 즉, P1이 먼저 실행되든 P2가 먼저 실행되든 반드시 P1 => P2 순서대로 실행된다.

12.2.3 모니터

image-20240501140305471.png

  • 세마포는 그 자체로 매우 훌륭한 프로세스 동기화 도구이지만, 사용하기가 조금 불편한 면이 있다.
    • 매번 임계 구역에 앞뒤로 일일이 wait와 signal 함수를 명시하는 것은 번거로운 일
    • 더군다나 자칫 아래처럼 잘못된 코드로 인해 예기치 못한 결과를 얻을 수도 있다.

image-20240501140501314.png

  • 최근에 등장한 동기화 도구가 모니터; montor
  • 모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어 관리
  • 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근

image-20240501140515886.png

  • 모니터를 통해 공유 자원에 접근하고자 하는 프로세스를 큐에 삽입하고,

  • 큐에 삽입된 순서대로 하나씩 공유 자원을 이용하도록 합니다.

  • 즉, 모니터는 공유 자원을 다루는 인터페이스에 접근하기 위한 큐(모니터에 진입하기 위한 큐) 를 만들고,

  • 모니터 안에 항상 하나의 프로세스만 들어오도록하여 상호 배제를 위한 동기화를 제공

  • 모니터는 세마포와 마찬가지로 실행 순서 제어를 위한 동기화도 제공합니다.

    • 특정 조건을 바탕으로 프로세스를 실행, 일시 중단하기 위해 조건 변수; condition variable를 사용하는데,
    • 조건 변수는 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수입니다.
    • 모니터가 조건 변수를 사용한다고는 하지만 조건 변수와 모니터는 별개의 개념
  • 조건 변수로는 wait와 signal 연산을 수행할 수 있다.

  • wait는 호출한 프로세스의 상태를 대기 상태로 전환하고 일시적으로 조건 변수에 대한 대기 큐에 삽입하는 연산

  • 여기서 헷갈리면 안 되는 점

    • 모니터에 진입하기 위해 삽입되는 큐(상호 배제를 위한 큐)
      • 모니터에 한 번에 하나의 프로세스만 진입하도록 하기 위해 만들어진 큐
    • wait가 호출되어 실행이 중단된 프로세스들이 삽입되는 큐 (조건 변수에 대한 큐) 는 다르다
      • 모니터에 이미 진입한 프로세스의 실행 조건이 만족될 때까지 잠시 실행이 중단되어 기다리기 위해 만들어진 큐

image-20240501140926841.png

  • 모니터에 진입한 어떤 프로세스가 x.wait() 를 통해 조건 변수 x에 대한 wait를 호출했다고 가정
    • 그 프로세스는 다음 그림처럼 조건 변수 x에 대한 큐에 삽입되므로 모니터는 다시 비게됩니다.
    • 그렇기에 다른 프로세스가 모니터 안에 들어올 수 있다.

image-20240501140952100.png

  • wait 연산으로 일시 중지된 프로세스는, 다른 프로세스의 signal 연산을 통해 실행이 재개

    • 즉, signal은 wait를 호출하여 큐에 삽입된 프로세스의 실행을 재개하는 연산입니다.

    • 가령 어떤 프로세스가 x.signal을 통해 조건 변수 x에 대한 signal을 호출했다고 가정

    • 이를 통해 조건 변수 x에 대해 대기 상태에 있던 프로세스가 깨어나 모니터 안으로 다시 들어옴

image-20240501140959329.png

  • 모니터 안에는 하나의 프로세스만이 있다.
    • 따라서 wait를 호출했던 프로세스는 signal을 호출한 프로세스가 모니터를 떠난 뒤에 실행되거나,
    • signal을 호출한 프로세스의 실행을 일시 중단하고, 자신이 실행된 뒤 다시 signal을 호출한 프로세스의 수행을 재개
    • 중요한 점은 모니터는 조건 변수를 이용하여 프로세스 실행 순서 제어를 위한 동기화를 제공
      • 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는, wait를 통해 실행을 중단
      • 특정 프로세스가 실행될 조건이 충족되었을 때에는, signal을 통해 실행을 재개

Reference