12.1 동기화란
- 협력하여 실행되는 프로세스들은 실행 순서와 자원의 일관성을 보장하기 위해 동기화(synchronization) 되어야 한다
12.1.1 동기화의 의미
- 프로세스 동기화: 프로세스들 사이의 수행 시기를 맞추는 것
- 실행 순서 제어: 프로세스를 올바른 순서대로 실행하기
- 상호 배제(mutual exclusion): 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
- 프로세스뿐만 아니라 스레드도 동기화 대상
- 정확히 말하면 실행의 흐름을 갖는 모든 것은 동기화의 대상
- 다만 이 책에서는 대부분의 전공서 표현에 따라 ‘프로세스 동기화’라고 칭함
실행 순서 제어를 위한 동기화
- Reader 프로세스는 Writer 프로세스 실행이 끝나야 비로소 실행할 수 있어야 한다
- Writer 가 Book.txt에 값을 저장하기도 전에 Reader 가 Book.txt를 읽는 것은 올바른 실행 순서가 아니다.
- Reader 는 ‘Book.txt 안에 값이 존재한다’는 특정 조건이 만족되어야만 실행을 이어나갈수 있다
- 프로세스를 올바른 순서대로 실행하는 것
상호 배제(mutual exclusion)를 위한 동기화
동시에 접근해서는 안 되는 자원에 동시에 접근하지 못하게 하는 것
두 개 이상의 프로세스가 임계 구역에 동시에 접근하지 못하도록 관리하는 것
프로세스 A와 B가 동시에 실행되었다고 가정
17만원이 계좌에 남을 거라고 유저는 기대
A와 B는 ‘잔액’이라는 데이터를 동시에 사용하는데, A가 끝나기도 전에 B가 잔액을 읽어 버렸기 때문에 엉뚱한 결과가 나옴
A와 B를 올바르게 실행하기 위해서는 한 프로세스가 잔액에 접근했을 때 다른 프로세스는 기다려야 한다
12.1.2 생산자와 소비자 문제
상호 배제를 위한 동기화에 관련된 고전적이고 유명한 문제로 생산자와 소비자 문제가 있다.
물건을 계속해서 생산하는 프로세스인 생산자
물건을 계속해서 소비하는 프로세스인 소비자
생산자와 소비자는 동시에 실행되는 스레드가 될 수도 있다.
생산자를 100,000번, 소비자를 100,000번 동시에 실행
유저는 총합 변수가 계속 10개로 머물러 있을 것으로 기대
하지만 결과는 다름: 생산자 프로세스와 소비자 프로세스가 제대로 동기화되지 않았기 때문에 발생한 문제
생산자와 소비자는 ‘총합’이라는 데이터를 동시에 사용하는데, 생산자가 소비자의 작업이 끝나기도 전에 총합을 수정해서때문에 엉뚱한 결과가 발생
한 마디로 동시에 접근해서는 안 되는 자원에 동시에 접근했기에 발생한 문제
12.1.3 공유 자원과 임계 구역
계좌 잔액 문제와 생산자 소비자 문제의 동시에 실행되는 프로세스들은 전역 변수 ‘잔액’, ‘총합’이라는 공동의 자원을 두고 작업을 했습니다.
이러한 자원을 공유 자원; shared resource 이라 한다.
공유 자원은 전역 변수, 파일, 입출력장치, 보조기억장치가 될 수도 있다.
임계 구역; critical section: 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역
- 임계 구역은 두 개 이상의 프로세스가 동시에 실행되면 안 되는 영역
- 두 개이 상의 프로세스가 임계 구역에 진입하고자 하면 둘 중 한는 대기해야 한다.
경쟁 상태; Race condition: 여러 프로세스가 동시 다발적으로 ‘‘임계 구역’‘의 코드를 실행하여 문제가 발생하는 경우
- 데이터 일관성이 깨지는 문제 발생
- 컴퓨터는 고급 언어가 아닌 저급 언어를 실행하기 때문에 여러 줄의 저급 언어로 변환된 고급 언어한 줄을 실행하는 과정에서 문맥 교환이 일어남.
- 저급 언어를 실행하는 과정에서 문맥교환이 일어난다면 아래와 같은 문제가 발생
‘상호 배제를 위한 동기화’ 를 위해서는 세 가지 원칙이 반드시 지켜져야만 한다.
- 상호 배제; mutual exclusion: 한 프로세스가 임계 구역에 진입했다면, 다른 프로세스는 임계 구역에 들어올 수 없다.
- 진행; progress: 임계 구역에 어떤 프로세스도 진입하지 않았다면, 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
- 유한 대기; bounded waiting: 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다 (임계 구역에 들어오기 위해 무한정 대기해서는 안 된다).
12.2 동기화 기법
12.2.1 뮤텍스 락
옷 가게의 탈의실을 이용하는 것에 비유
손님-프로세스, 탈의실-임계 구역
자물쇠가 걸려 있지 않다면 탈의실을 이용하면 된다, 걸려 있다면 대기
뮤텍스 락; MUTual EXclusion Lock: 이 자물쇠 기능을 코드로 구현한 것
상호 배제를 위한 동기화 도구
임계 구역에 진입하는 프로세스는 ‘내가 지금 임계 구역에 있음’을 알리기 위해 뮤텍스 락을 이용해 임계 구역에 자물쇠를 걸어둘 수 있고
다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨 있지 않다면 임계 구역에 진입할 수 있다.
뮤텍스 락의 단순한 형태는 하나의 전역 변수와 두 개의 함수로 구현 가능
- 자물쇠 역할: 프로세스들이 공유하는 전역 변수 lock
- 임계 구역을 잠그는 역할: acquire 함수
- 임계 구역의 잠금을 해제하는 역할: release 함수
acquire 함수는 프로세스가 임계 구역에 진입하기 전에 호출하는 함수
- 만일 임계 구역이 잠겨 있다면 임계 구역이 열릴 때까지 (lock이 false가 될 때까지) 임계 구역을 반복적으로 확인하고,
- 마치 탈의실 문이 잠겨 있는지 쉴 새 없이 반복하며 확인해 보는 것과 같다.
- 이런 대기 방식을 바쁜 대기; busy wait 이라 한다.
- 임계 구역이 열려 있다면 임계 구역을 잠그는 (lock을 true로 바꾸는) 함수
- 만일 임계 구역이 잠겨 있다면 임계 구역이 열릴 때까지 (lock이 false가 될 때까지) 임계 구역을 반복적으로 확인하고,
release 함수는 임계 구역에서의 작업이 끝나고 호출하는 함수
- 현재 잠긴 임계 구역을 열어주는 (lock을 false로 바꾸는) 함수
이렇게 되면 프로세스는
- 락을 획득할 수 없다면 (임계 구역에 진입할 수 없다면) 무작정 기다리고,
- 락을 획득할 수 있다면 (임계 구역에 진입할 수 있다면) 임계 구역을 잠근 뒤 임계 구역에서의 작업을 진행하고,
- 임계 구역에서 빠져나올 때엔 다시 임계 구역의 잠금을 해제함으로써 임계 구역을 보호할 수 있다.
12.2.2 세마포
세마포; semaphore는 뮤텍스 락과 비슷하지만, 조금 더 일반화된 방식의 동기화 도구
- 엄밀히 말하면 세마포의 종류에도 이진 세마포(binary)와 카운팅 세마포(counting)가 존재
- 이진 세마포는 뮤텍스 락과 비슷한 개념이므로 여기서는 여러 공유 자원을 다룰 수 있는 카운팅 세마포를 다룬다
탈의실이 여러 개 있는 상황처럼 공유 자원이 여러 개 있을 경우 (각 공유 자원에는 하나의 프로세스만 진입이 가능할지라도) 여러 개의 프로세스가 각각 공유 자원에 접근이 가능해야 한다.
- 한 번에 하나의 프로세스만 이용할 수 있는 프린터 세 대가 있는 상황에서
- 하나의 프린터를 사용할 수 있는 프로세스는 하나이지만, 총 세 개의 프로세스가 공유 자원 (세 대의 프린터) 을 이용할 수 있다.
세마포는 공유 자원이 여러 개 있는 상황에서도 적용이 가능한 동기화 도구
세마포는 뮤텍스 락과 비슷하게 하나의 변수와 두 개의 함수로 단순하게 구현할 수 있다.
- 전역변수 S: 임계 구역에 진입할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수)
- wait 함수: 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는 함수
- signal 함수: 임계 구역 앞에서 기다리는 프로세스에 ‘이제 가도 좋다’고 신호를 주는 함수
- 여기에서 세마포 변수와 함수 이름을 각각 S, wait, signal로 나타내지만, 변수, 함수 이름은 전공서마다 다를 수 있다.
- 일부에서는 세마포를 처음 개발한 다익스트라에 근거해 wait와 signal 함수를 P, V로 명명
- 일부에서는 철도 신호기에 근거해 down, up으로 명명
- 다만 변수와 함수를 어떻게 지칭하든지 원리와 작동 방식은 모두 동일
wait 함수
- 만일 임계 구역에 진입할 수 있는 프로세스 개수가 0 이하라면
- 사용할 수 있는 자원이 있는지 반복적으로 확인하고,
- 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1 감소시키고 임계 구역 진입한다.
signal 함수
- 임계 구역에서의 작업을 마친 뒤 S를 1 증가
여기서 한 가지 문제가 있습니다.
- 이는 뮤텍스 락에도 해당되는 문제
- 사용할 수 있는 공유 자원이 없는 경우 프로세스는 무작정 무한히 반복하며 전역 변수 S를 확인해야 합니다.
- 이렇게 바쁜 대기(busy wait) 를 반복하며 확인할 시간에 CPU는 더 생산성 있는 작업을 할 수 있을 텐데, CPU 주기를 낭비한다는 점에서 손해
그래서 실제로 세마포는 다른 더 좋은 방법을 사용
- wait 는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태를 대기 상태로 만들고,
- 그 프로세스의 PCB를 세마포를 위한 대기 큐에 집어 넣음
- 다른 프로세스가 임계 구역에서의 작업이 끝나고 signal 를 호출하면 signal 는 대기 중인 프로세스를 대기 큐에서 제거하고,
- 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨줌.
- 지금까지는 상호 배제를 위한 동기화 기법
- 아래는, 세마포를 이용해 실행 순서 제어를 위한 동기화(프로세스의 순서를 제어하는 방법) 에 대해 설명
- 세마포를 이용하면 동시에 실행되는 프로세스의 실행 순서도 원하는 대로 제어할 수 있다.
- 세마포의 변수 S를 0으로 두고 먼저 실행할 프로세스 뒤에 signal 함수, 다음에 실행할 프로세스 앞에 wait 함수를 붙이면 됩니다.
- P1이 먼저 실행되면 P1이 임계 구역에 먼저 진입하는 것은 자명
- P2가 먼저 실행 되더라도 P2는 wait 함수를 만나므로 P1이 임계 구역에 진입
- 그리고 P1이 임계 구역의 실행을 끝내고 signal을 호출하면 그제서야 P2가 임계 구역에 진입
- 즉, P1이 먼저 실행되든 P2가 먼저 실행되든 반드시 P1 => P2 순서대로 실행된다.
12.2.3 모니터
- 세마포는 그 자체로 매우 훌륭한 프로세스 동기화 도구이지만, 사용하기가 조금 불편한 면이 있다.
- 매번 임계 구역에 앞뒤로 일일이 wait와 signal 함수를 명시하는 것은 번거로운 일
- 더군다나 자칫 아래처럼 잘못된 코드로 인해 예기치 못한 결과를 얻을 수도 있다.
- 최근에 등장한 동기화 도구가 모니터; montor
- 모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어 관리
- 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근
모니터를 통해 공유 자원에 접근하고자 하는 프로세스를 큐에 삽입하고,
큐에 삽입된 순서대로 하나씩 공유 자원을 이용하도록 합니다.
즉, 모니터는 공유 자원을 다루는 인터페이스에 접근하기 위한 큐(모니터에 진입하기 위한 큐) 를 만들고,
모니터 안에 항상 하나의 프로세스만 들어오도록하여 상호 배제를 위한 동기화를 제공
모니터는 세마포와 마찬가지로 실행 순서 제어를 위한 동기화도 제공합니다.
- 특정 조건을 바탕으로 프로세스를 실행, 일시 중단하기 위해 조건 변수; condition variable를 사용하는데,
- 조건 변수는 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수입니다.
- 모니터가 조건 변수를 사용한다고는 하지만 조건 변수와 모니터는 별개의 개념
조건 변수로는 wait와 signal 연산을 수행할 수 있다.
wait는 호출한 프로세스의 상태를 대기 상태로 전환하고 일시적으로 조건 변수에 대한 대기 큐에 삽입하는 연산
여기서 헷갈리면 안 되는 점
- 모니터에 진입하기 위해 삽입되는 큐(상호 배제를 위한 큐) 와
- 모니터에 한 번에 하나의 프로세스만 진입하도록 하기 위해 만들어진 큐
- wait가 호출되어 실행이 중단된 프로세스들이 삽입되는 큐 (조건 변수에 대한 큐) 는 다르다
- 모니터에 이미 진입한 프로세스의 실행 조건이 만족될 때까지 잠시 실행이 중단되어 기다리기 위해 만들어진 큐
- 모니터에 진입하기 위해 삽입되는 큐(상호 배제를 위한 큐) 와
- 모니터에 진입한 어떤 프로세스가 x.wait() 를 통해 조건 변수 x에 대한 wait를 호출했다고 가정
- 그 프로세스는 다음 그림처럼 조건 변수 x에 대한 큐에 삽입되므로 모니터는 다시 비게됩니다.
- 그렇기에 다른 프로세스가 모니터 안에 들어올 수 있다.
wait 연산으로 일시 중지된 프로세스는, 다른 프로세스의 signal 연산을 통해 실행이 재개
즉, signal은 wait를 호출하여 큐에 삽입된 프로세스의 실행을 재개하는 연산입니다.
가령 어떤 프로세스가 x.signal을 통해 조건 변수 x에 대한 signal을 호출했다고 가정
이를 통해 조건 변수 x에 대해 대기 상태에 있던 프로세스가 깨어나 모니터 안으로 다시 들어옴
- 모니터 안에는 하나의 프로세스만이 있다.
- 따라서 wait를 호출했던 프로세스는 signal을 호출한 프로세스가 모니터를 떠난 뒤에 실행되거나,
- signal을 호출한 프로세스의 실행을 일시 중단하고, 자신이 실행된 뒤 다시 signal을 호출한 프로세스의 수행을 재개
- 중요한 점은 모니터는 조건 변수를 이용하여 프로세스 실행 순서 제어를 위한 동기화를 제공
- 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는, wait를 통해 실행을 중단
- 특정 프로세스가 실행될 조건이 충족되었을 때에는, signal을 통해 실행을 재개
Reference
- 혼자 공부하는 컴퓨터구조+운영체제 (강민철, 한빛미디어) https://product.kyobobook.co.kr/detail/S000061584886