저번 시간에 프로세스와 쓰레드는 동시다발적으로 수행된다고 배웠습니다.
- 동시다발적으로 실행되는 프로세스들은 서로 협력하며 영향을 주고 받기도 합니다.
- 이 과정에서
자원의 일관성을 보장해야 합니다. - 이렇게 협력하여 실행되는 프로세스들은 실행 순서와 자원의 일관성을 보장해야 하기에,
- 반드시
동기화(synchronization)되어야 합니다.
이번 절에서 동기화 개념을 정리하고, 프로세스를 동기화하지 않을 경우 발생할 수 있는 문제들을 통해 동기화가 왜 필요한지 알아봅니다.
1. 동기화
동시다발적으로 실행되는 많은 프로세스는 서로 데이터를 주고받으며 협력하며 실행될 수 있습니다.
- e.g. 워드 프로세서의 맞춤법 검사 프로세스, 입력 내용을 화면에 출력하는 프로세스 등
- 이 프로세스들은 각기 다른 독립적인 프로세스이지만, 공동의 목표를 위해 서로 협력하는 존재
이렇게 협력적으로 실행되는 프로세스들은 아무렇게나 마구 동시에 실행해서는 안됩니다.
올바른 실행을 위해서는 동기화가 필수입니다.
프로세스 동기화란 무엇일까요?
- 국어 사전 : ‘정보·통신 분야에서의 동기화란 작업들 사이의 수행 시기를 맞추는 것’
- 즉, 프로세스들 사이의 수행 시기를 맞추는 것
- 사전에 나오는 동기화의 의미는 무엇을 의미할까요? 크게 다음 2가지를 일컫습니다.
실행 순서 제어: 프로세스를 올바른 순서대로 실행하기상호 배제: 동시에 접근해서는 안되는 자원에 하나의 프로세스만 접근하게 하기
💡 프로세스뿐만 아니라 스레드도 동기화 대상
실행의 문맥을 갖는 모든 대상은 동기화 대상이기에 프로세스뿐만 아니라 스레도드 동기화 대상입니다. 다만 이 책에서는 대부분의 전공서 표현에 따라
프로세스 동기화라고 칭하겠습니다.동기화
- 특정 자원에 접근할 때 한 개의 프로 세스만 접근하게 하거나,
- 프로세스를 올바른 순서대로 실행하게 하는 것
1.1 실행 순서 제어를 위한 동기화

실행 순서 제어를 위한 동기화 : reader writer problem
e.g. Writer라는 프로세스와 Reader라는 프로세스가 동시에 실행 중이라고 가정한다면,
Writer 프로세스: Book.txt 파일에 값을 저장하는 프로세스Reader 프로세스: Book.txt 파일에 저장된 값을 읽어 들이는 프로세스- Reader와 Writer 프로세스는 무작정 아무렇게나 실행되어서는 안됩니다.
- 왜냐하면 실행의 순서가 있기 때문입니다.
Reader 프로세스는 ‘Book.txt’ 안에 값이 존재한다는 특정 조건이 만족되어야만 실행 가능합니다.- 즉, 동시다발적으로 실행되는 2개 이상의 프로세스가 있을 떄,
- 실행 순서를 제어하도록 실행 순서를 동기화해주는 것이
실행 순서 제어를 위한 동기화라고 할 수 있습니다.
1.2 상호 배제를 위한 동기화
상호 배제(mutual exclusion)를 위한 동기화- 공유가 불가능한 자원의 동시 사용을 피하기 위해 동기화를 사용하는 알고리즘
- 고전적이고, 대표적인 상호배제를 위한 동기화 예시들
- Bank account problem (은행 계좌 문제)
- Producer & Consumer problem (생산자와 소비자 문제)
1.2.1 계좌 잔액 문제
- 현재 계좌에 잔액 : 10만원
- 프로세스A는 현재 잔액에 2만원을 추가하는 프로세스
- 프로세스B는 현재 잔액에 5만원을 추가하는 프로세스
프로세스 A가 실행되는 과정을 자세히 표현해보면, 다음과 같이 나타낼 수 있습니다.
- 계좌의 잔액을 읽어 들인다.
- 읽어 들인 잔액에 2만원을 더한다.
- 더한 값을 저장한다.
마찬가지로 프로세스 B가 실행되는 과정은 다음과 같은 순서로 나타낼 수 있습니다.
- 계좌의 잔액을 읽어 들인다.
- 읽어 들인 잔액에 5만원을 더한다.
- 더한 값을 저장한다

프로세스A와 B가 동시에 실행된다고 가정하면,
- 당연히 실행 결과 17만 원이 계좌에 남을 것을 기대할 것입니다.
- 하지만 동기화가 제대로 이루어지지 않은 경우 위 그림과 같이 전혀 엉뚱한 결과가 나올 수 있습니다.
- 왜 이런 일이 발생했을까요?
- A와 B는 ‘잔액’이라는 데이터를 동시에 사용하는데,
- A가 끝나기도 전에, B가 잔액을 읽어버렸기 때문에 엉뚱한 결과가 나온 것입니다.

A와 B를 올바르게 실행하기 위해서는 한 프로세스가 잔액에 접근했을 때, 다른 프로세스는 기다려야 합니다.
이렇게 동시에 접근해서는 안되는 자원에 동시에 접근하지 못하게 하는 것이 상호 배제를 위한 동기화입니다.
1.2.2 생산자와 소비자 문제
- 물건을 계속해서 생산하는 프로세스인
생산자(producer, 프로세스 혹은 스레드) - 물건을 계속해서 소비하는 프로세스인
소비자(consumer, 프로세스 혹은 스레드) - 생산자와 소비자는
‘총합’이라는 데이터를 공유하고 있습니다.
생산자와 소비자는 대략 다음과 같이 실행된다고 가정합니다.
1생산자 () {2버퍼에 데이터 삽입3‘총합’ 변수 1 증가4}
1소비자 () {2버퍼에서 데이터 빼내기3‘총합’ 변수 1 감소4}
- 생산자는 버퍼에 물건을 넣은 후, 물건의 총합에 해당하는 변수를 1 증가시키고,
- 소비자는 버퍼에 물건을 빼낸 후, 물건의 총합에 해당하는 변수를 1 감소시킵니다.
물건이 처음에 10개 있었다고 가정해 봅시다. 다시 말해, 물건의 총합 변수를 10으로 초기화해 봅시다.
1총합 = 1023생산자 () {4버퍼에 데이터 삽입5‘총합’ 변수 1 증가6}78소비자 () {9버퍼에서 데이터 빼내기10‘총합’ 변수 1 감소11}
위와 같은 상태에서 생산자를 100,000번, 소비자를 100,000번 동시에 실행해보면?
- 때로는 0과 다른 값이 되거나, 오류가 발생하기도 합니다.
- 아래 내용은 생산자와 소비자 코드를 실제로 구현하고 실행한 결과입니다.
- 아래 링크 producer_consumer 항목을 참고하세요.
💡 예제 코드 다운로드하기
https://github.com/kangtegong/self-learning-cs/tree/main/producer_consumer
초기 합계 : 10 producer, consumer 스레드 실행 이후 합계 : 63078
초기 합계 : 10 producer, consumer 스레드 실행 이후 합계 : -13750
이는 생산자 프로세스와 소비자 프로세스가 제대로 동기화되지 않았기 때문에 발생한 문제입니다.
- 생산자와 소비자는 ‘총합’이라는 데이터를 동시에 사용하는데,
- 앞서의 예제에서는 소비자가 생산자의 작업이 끝나기도 전에 총합을 수정했고,
- 생산자가 소비자의 작업이 끝나기도 전에 총합을 수정했기 때문에 엉뚱한 결과가 발생한 것이지요.
한 마디로 계좌 잔액 문제와 생산자 소비자 문제는 동시에 접근해서는 안되는 자원에 동시에 접근했기에 발생한 문제라고 볼 수 있습니다.
1.3 공유 자원과 임계 구역
그렇다면 동시에 접근해서는 안 되는 자원이란 무엇일까요?
1.3.1 공유 자원(shared resource)
- 여러 프로세스 혹은 스레드가 공유하는 자원
- e.g. 계좌 잔액 문제에서 ‘잔액’ 변수,
- e.g. 생산자 소비자 문제에서 ‘총합’ 변수
- cf. 공유 자원은 전역 변수가 될 수도, 파일이 될 수도, 입출력장치, 보조기억장치가 될 수도 있습니다.
1.3.2 임계구역(critical section)
- 공유 자원 중에는 2개 이상의 프로세스를 동시에 실행하면, 문제가 발생하는 자원
- 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역
- e.g. 계좌 잔액 문제에서 ‘잔액’ 변수,
- e.g. 생산자 소비자 문제에서 ‘총합’ 변수

2개 이상의 프로세스가 임계 구역에 진입하고자 하면, 둘 중 하나는 대기해야 합니다.
임계 구역에 먼저 진입한 프로세스의 작업이 마무리되면, 그제서야 기다렸던 프로세스가 임계 구역에 진입합니다.
1.3.3 레이스 컨디션(race condition)
임계 구역은 2개 이상의 프로세스가 동시에 실행되면 안 되는 영역이지만,
잘못된 실행으로 인해 여러 프로세스가 동시 다발적으로 임계 구역의 코드를 실행하여,
문제가 발생하는 경우가 있습니다. 이를 레이스 컨디션이라고 합니다.
레이스 컨디션이 발생하면, 계좌 잔액 문제나 생산자와 소비자 문제처럼 데이터의 일관성이 깨지는 문제가 발생합니다. 즉, 계좌 잔액 문제와 생산자와 소비자 문제는 레이스 컨디션의 사례로 볼 수 있습니다.
레이스 컨디션이 발생하는 근본적인 이유를 따져 봅시다.
03장에서 고급 언어는 실행 과정에서 저급 언어로 변환되어 실행된다고 했던 것을 기억하나요?

e.g. 생산자 소비자 문제에서
- ‘총합을 1 증가시킨다’ 혹은 ‘총합을 1 감소시킨다’라는 코드는 고급 언어 1줄로 작성할 수 있지만,
- 이는 컴퓨터 내부에서 대략 위 그림과 같이 여러 줄의 저급 언어로 변환되어 실행됩니다.

컴퓨터는 고급 언어가 아닌 저급 언어를 실행하기 때문에,
- 여러 줄의 저급 언어로 변환된 고급 언어 한 줄을 실행하는 과정에서 문맥 교환이 일어날 수 있습니다.
- 저급 언어를 실행하는 과정에서 문맥 교환이 일어난다면, 위와 같은 문제가 발생합니다.
이때, 상호 배제를 위한 동기화는 이와 같은 일이 발생하지 않도록,
2개 이상의 프로세스가 임계 구역에 동시에 접근하지 못하도록 관리하는 것을 의미합니다.
운영체제는 이러한 임계 구역 문제를 아래 3가지 원칙 하에 해결합니다. 달리 말해, 상호 배제를 위한 동기화를 위해서는 아래 3가지 원칙이 반드시 지켜져야만 합니다.
-
상호 배제(mutual exclusion)- 한 프로세스가 임계 구역에 진입했다면, 다른 프로세스는 임계 구역에 들어올 수 없다.
-
진행(progress)- 임계 구역에 어떤 프로세스도 진입하지 않았다면,
- 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
-
유한 대기(bounded waiting)- 한 프로세스가 임계 구역에 진입하고 싶다면,
- 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다.
- (즉, 임계 구역에 들어오기 위해 무한정 대기해서는 안된다)
다음 절에서는 운영체제가 이 원칙들을 토대로 어떻게 상호 배제를 위한 동기화를 이루는지, 나아가 어떻게 실행 순서 제어를 위한 동기화를 이루는지 알아보겠습니다.
2. 공유 자원과 임계 구역
프로세스 간의 동기화는 여러 가지 방법으로 할 수 있습니다.
- 그렇지만 이번에는 대부분의 전공서가 소개하고,
- 가장 대중적으로 알려져 있는 3가지(뮤텍스 락, 세마포, 모니터)에 대해 설명하겠습니다.
- 이것들이 어떻게 상호배제를 이루는지,
- 그리고 실행 순서를 제어하기 위한 동기화를 어떻게 이루는지 알아봅시다.
💡 동기화의 종류
- 실행 순서 제어를 위한 동기화
- 상호 배제를 위한 동기화
2.1 뮤텍스 락 (Mutex lock)
2.1.1 상호 배제를 위한 동기화

뮤텍스 락(Mutex lock; MUTual EXclusion lock)=== 상호 배제를 위한 동기화 도구- e.g. 동기화를 옷 가게에서 탈의실을 이용하는 것에 비유해보면,
- 옷 가게에서 마음에 드는 옷이 있으면 손님은 탈의실에 들어가서 옷을 입어볼 수 있습니다.
- 이때 탈의실에는 1명의 인원만 들어갈 수 있습니다.
- 손님들은 탈의실이라는 자원을 이용하고, 탈의실 안에는 손님 1명씩만 들어올 수 있으니,
- 손님은 ‘프로세스’, 탈의실은 ‘임계 구역’이라고 할 수 있습니다.
- e.g. 만일 밖에서 탈의실에 사람이 있는지 없는지 알 수 없는 상황이라면,
- 여러분은 어떻게 탈의실이 이용 중임을 알 수 있나요?
- 일단 탈의실을 열어보고 자물쇠가 걸려 있다면,
- 탈의실 안에 사람이 있다고 판단하고 기다립니다.
- 자물쇠가 걸려 있지 않다면, 탈의실을 이용하면 되는 것입니다.
- 이 자물쇠 기능을 코드로 구현한 것이
뮤텍스 락입니다.- 동시에 접근해서는 안되는 자원에 동시에 접근하지 않도록 만드는 도구
- 다시 말해, 상호 배제를 위한 동기화 도구
뮤텍스 락의 매우 단순한 형태는 하나의 전역 변수와 2개의 함수로 구현할 수 있습니다.
-
자물쇠 역할: 프로세스들이 공유하는 전역 변수 lock -
임계 구역을 잠그는 역할: acquire 함수- 프로세스가 임계 구역에 진입하기 전에 호출하는 함수
- 만일 임계 구역이 잠겨 있다면,
- 임계 구역이 열릴 때까지(lock이 false가 될 때까지) 임계 구역을 반복적으로 확인하고,
- 임계 구역이 열려있다면 임계 구역을 잠그는(lock을 true로 바꾸는) 함수
-
임계 구역의 잠금을 해제하는 역할: release 함수- 프로세스가 임계 구역에서의 작업이 끝나고 호출하는 함수
- 현재 잠긴 임계 구역을 열어주는(lock을 false로 바꾸는) 함수
2.1.2 acquire()와 release()
코드로 구현해보면,
1acquire() {2while (lock == true) /* 만약 임계 구역이 잠겨 있다면 */3; /* 임계 구역이 잠겨 있는지를 반복적으로 확인 */4lock = true; /* 만약 임계 구역이 잠겨 있지 않다면 임계 구역 잠금 */5}67release() {8lock = false; /* 임계 구역 작업이 끝났으니 잠금 해제 */9}
acquire와 release 함수를 다음과 같이 임계 구역 전후로 호출함으로써,
하나의 프로세스만 임계 구역에 진입할 수 있습니다.
1acquire(); // 자물쇠 잠겨 있는지 확인, 잠겨 있지 않다면 잠그고 들어가기2// 임계 구역 // 임계 구역에서의 작업 진행3release(); // 자물쇠 반환
앞서 언급한 생산자 소비자 문제 또한 아래와 같이 뮤텍스로 구현할 수 있겠죠?
1acquire();2// ‘총합’ 변수 접근3release();
이렇게 되면 프로세스는
- 락을 획득할 수 없다면(임계 구역에 진입할 수 없다면) 무작정 기다리고,
- 락을 획득할 수 있다면(임계 구역에 진입할 수 있다면) 임계 구역을 잠근 뒤 임계 구역에서의 작업을 진행하고,
- 임계 구역에서 빠져나올 때엔 다시 임계 구역의 잠금을 해제함으로써 임계 구역을 보호할 수 있습니다.
참고로 acquire 함수를 다시 보면 임계 구역이 잠겨 있을 경우, 프로세스는 반복적으로 lock을 확인하는 것을 알 수 있습니다.
1while (lock == true) /* 만약 임계 구역이 잠겨 있다면 */2; /* 임계 구역이 잠겨 있는지를 반복적으로 확인 */
이는 마치 탈의실 문이 잠겨 있는지 쉴 새 없이 반복하며 확인해 보는 것과 같습니다.
이런 대기 방식을 바쁜 대기(busy wait)라고 합니다.
💡 acquire, release 함수를 제공하는 프로그래밍 언어들
- C/C++, python 등의 일부 프로그래밍 언어에서는
- 사용자가 직접 acquire, release 함수를 구현하지 않도록, 뮤텍스 락 기능을 제공합니다.
- 실제 프로그래밍 언어가 제공하는 뮤텍스 락은 앞서 소개한 구현보다 훨씬 정교하게 설계되어 있습니다.
2.2 세마포 (semaphore)
2.2.1 상호 배제를 위한 동기화

- 세마포는 뮤텍스 락과 비슷하지만, 조금 더 일반화된 방식의 동기화 도구
뮤텍스 락: 하나의 공유 자원에 접근하는 프로세스를 상정한 방식- 즉, 탈의실이 하나 있는 경우를 가정하고 만든 동기화 도구
세마포: 공유 자원이 여러 개 있는 경우에도 적용 가능- (각 공유 자원에는 하나의 프로세스만 진입이 가능할지라도)
- 탈의실이 여러 개 있는 상황처럼, 공유 자원이 여러 개 있을 경우를 가정하고 만든 동기화 도구
- cf. 얼밀히 말하면, 세마포에도 종류가 있습니다.
이진 세마포(binary semaphore)와카운팅 세마포(counting semaphore)가 있지만,- 이진 세마포는 뮤텍스 락과 비슷한 개념이므로,
- 이 책에서는 여러 공유 자원을 다룰 수 있는
카운팅 세마포를 다루겠습니다.

‘세마포’라는 단어는 위 그림과 같이 철도 신호기에서 유래한 단어입니다.
cf. 열차 신호기(교통신호등)를 영어로 세마포(semaphore)라고 합니다.
- 기차는 신호기가 내려가 있을 때는 ‘멈춤’ 신호로 간주하고 잠시 멈춥니다.
- 반대로 신호기가 올라와 있을 때는 ‘가도 좋다’는 신호로 간주하고 다시 움직이기 시작합니다.
세마포는 이와 같이 ‘멈춤’ 신호와 ‘가도 좋다’는 신호로서 임계 구역을 관리합니다.- 즉,
프로세스는임계 구역앞에서 멈춤 신호를 받으면 잠시 기다리고, - 가도 좋다는 신호를 받으면, 그제서야 임계 구역에 들어가게 됩니다.
- 즉,
세마포가 어떻게 구현되는지 간략하게 살펴봅시다. 세마포는 뮤텍스 락과 비슷하게 하나의 변수와 2개의 함수로 단순하게 구현할 수 있습니다.
- 임계 구역에 진입할 수 있는 **프로세스의 개수(사용 가능한 공유 자원의 개수)**를 나타내는
전역 변수 S - 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는
wait 함수 - 임계 구역 앞에서 기다리는 프로세스에 ‘가도 좋다’고 신호를 주는
signal 함수
💡 세마포 변수와 함수 이름
참고로 이 책은 세마포 변수와 함수 이름을 각각 S, wait, signal로 나타내지만, 변수 이름과 함수 이름은 전공서마다 다를 수 있습니다.
- 일부 전공서에서는
- 세마포를 처음 개발한 네덜란드의 컴퓨터과학자
- 에츠허르 다익스트라(Edsger W. Dijkstra)의 모국어에 근거해
wait와 signal 함수를P, V로 명명하기도 하고,- 일부 전공서에서는
- 철도 신호기에 근거해
down, up으로 명명하기도 합니다.다만 변수와 함수를 어떻게 지칭하든지, 원리와 작동 방식은 모두 동일합니다.
뮤텍스 락을 사용할 때, 임계 구역 진입 전후로 acquire()와 release()를 호출했듯이
세마포도 임계 구역 진입 전후로 wait()와 signal()을 호출합니다.
1wait()2// 임계 구역3signal()
(1) wait()
변수 S는 임계 구역에 진입할 수 있는 프로세스의 개수, 혹은 사용 가능한 공유 자원의 개수라고 했죠? 이를 토대로 생각해 보았을 때, wait 함수는 아래와 같이 만듭니다.
1wait () {2while ( S <= 0 ) /* 1 만일 임계 구역에 진입할 수 있는 프로세스 개수가 0 이하라면 */3; /* 2 사용할 수 있는 자원이 있는지 반복적으로 확인하고, */4S--; /* 3 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1 감소시키고 임계 구역 진입 한다.*/5}
(2) signal()
signal 함수는 다음과 같이 만듭니다.
1signal () {2S++ /* 1 임계 구역에서의 작업을 마친 뒤 S를 1 증가시킨다. */3}

e.g. 세 개의 프로세스 P1, P2, P3가 2개의 공유 자원에 P1, P2, P3 순서로 접근한다고 가정해봅시다. 공유 자원이 2개 있으니 변수 S는 2가 되겠죠? 그렇다면 다음과 같은 순서로 실행됩니다.
- 프로세스 P1 wait 호출. S는 현재 2이므로 S를 1 감소시키고 임계 구역 진입
- 프로세스 P2 wait 호출. S는 현재 1이므로 S를 1 감소시키고 임계 구역 진입
- 프로세스 P3 wait 호출. S는 현재 0이므로 무한히 반복하며 S 확인
- 프로세스 P1 임계 구역 작업 종료, signal() 호출. S를 1 증가
- 프로세스 P3 S가 1이 됨을 확인. S는 현재 1이므로 S를 1 감소시키고 임계 구역 진입
여기서 한 가지 문제가 있습니다. 이는 앞서 설명한 뮤텍스 락에도 해당되는 문제인데,
- 사용할 수 있는 공유 자원이 없는 경우, 프로세스는 무작정 무한히 반복하며 S를 확인해야 합니다.
- 이는 마치 탈의실 문이 잠겨 있는지 아닌지 계속 반복해서 확인하는 것과 같습니다.
- 뮤텍스 락 절에서 이를
Busy waiting이라고 배웠습니다. - 이는 CPU 주기를 낭비하는 것입니다.
그래서 실제로 세마포는 다른 더 좋은 방법을 사용합니다.
wait 함수는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태를 대기 상태로 만들고,- 해당 프로세스의PCB를 세마포를 위한 대기 큐에 집어넣습니다.
- 그리고 다른 프로세스가 임계 구역에서의 작업이 끝나고 signal 함수를 호출하면
signal 함수는 대기 중인 프로세스를 대기 큐에서 제거하고,- 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨줍니다.
이를 간단한 코드로 나타내면 다음과 같습니다.
1wait() {2S--;3if ( S < 0 ) {4add this process to Queue; /* 1 해당 프로세스 PCB를 대기 큐에 삽입한다. */5sleep(); /* 2 대기 상태로 접어든다. */6}7}
1signal() {2S++3if ( S <= 0 ) {4remove a process p from Queue /* 1 대기 큐에 있는 프로세스 p를 제거한다. */5wakeup(p) /* 2 프로세스 p를 대기 상태에서 준비 상태로 만든다. */6}7}

앞선 예시와 마찬가지로 공유 자원은 2개, 접근하려는 프로세스는 P1, P2, P3 3개이고,
P1, P2, P3 순서로 임계 구역에 접근한다고 가정해 봅시다. 공유 자원이 2개이니 S는 2입니다.
-
프로세스 P1 wait 호출. S를 1 감소시키면 S는 1이므로 임계 구역 진입
-
프로세스 P2 wait 호출. S를 1 감소시키면 S는 0이므로 임계 구역 진입
-
프로세스 P3 wait 호출. S를 1 감소시키면 S는 -1이므로 본인의 PCB를 대기 큐에 넣고 대기 상태로 전환
-
프로세스 P1 임계 구역 작업 종료, signal() 호출.
- S를 1 증가하면 0이므로 대기 상태였던 P3 를 대기 큐에서 꺼내 준비 큐로 옮겨줌
-
깨어난 프로세스 P3 임계 구역 진입
-
프로세스 P2 임계 구역 작업 종료, signal() 호출. S가 1 증가하면 1
-
프로세스 P3 임계 구역 작업 종료, signal() 호출. S가 1 증가하면 2
조금 복잡하게 느껴질 수는 있지만, 위 과정을 외울 필요는 없습니다.
- 변수 S가 무엇 을 의미하는지, wait, signal 함수의 코드가 무엇을 의미하는지만 안다면,
- 위 과정과 그림은 여러분도 충분히 그릴 수 있습니다.
- 지금으로서는 원리를 납득하는 데만 집중하세요.
지금까지 설명한 내용은 세마포를 이용한 상호 배제를 위한 동기화 기법이었습니다.
💡 동기화의 종류
- 실행 순서 제어를 위한 동기화
- 특정 조건이 만족되어야만 실행할 수 있는 상황에서 올바른 순서대로 실행하게 하는 것
- 상호 배제를 위한 동기화
- 동시에 접근해서는 안되는 자원에 동시에 접근하지 않도록 제어하는 것
2.2.2 실행 순서 동기화
이번에는 세마포를 이용해 프로세스의 순서를 제어하는 방법에 대해 알아보도록 합시다.
방법은 간단합니다.
세마포의 변수 S를0으로 두고,- 먼저 실행할 프로세스
뒤에 signal함수, - 다음에 실행할 프로세스
앞에 wait 함수를 붙이면 됩니다.

위 그림을 보면,
- P1이 먼저 실행되면, P1이 임계 구역에 먼저 진입하는 것은 자명한 일이고,
- P2가 먼저 실행되더라도, P2는 wait 함수를 만나므로 P1이 임계 구역에 진입합니다.
- 그리고 P1이 임계 구역의 실행을 끝내고 signal을 호출하면, 그제서야 P2가 임계 구역에 진입합니다.
- 즉, P1이 먼저 실행되든, P2가 먼저 실행되든, 반드시 P1, P2 순서대로 실행됩니다.
- cf. 세마포도 뮤텍스 락과 마찬가지로 많은 프로그래밍 언어에서 제공합니다.
2.3 모니터

세마포는 그 자체로 매우 훌륭한 프로세스 동기화 도구이지만, 사용하기가 조금 불편한 면이 있습니다.
- 매번 임계 구역에 앞뒤로 일일이 wait와 signal 함수를 명시하는 것은 번거로운 일이기 때문입니다.
- 더군다나 위 그림처럼 잘못된 코드로 인해 예기치 못한 결과를 얻을 수도 있습니다.
- 설마 저런 것을 헷갈릴까 싶은 독자들이 있을지 모르나 코드가 방대해지고 복잡해지면,
- 위와 같은 상황은 얼마든지 발생할 수 있습니다.

그래서 최근에 등장한 동기화 도구가 모니터(monitor)입니다.
- 모니터는 세마포에 비하면 사용자가 사용하기에 훨씬 편리한 도구입니다.
- 모니터는 위 그림처럼 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어 관리합니다.
- 그리고 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근하도록 합니다.
2.3.1 상호 배제를 위한 동기화

- 인터페이스를 위한 큐
- 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어 관리
- 큐에 삽입된 순서대로 (한 번에 하나의 프로세스만) 공유 자원 이용
- 공유 자원에 접근하고자 하는 프로세스와 스레드는 반드시 특정 인터페이스를 통해서만 접근
- 공유 자원에는 하나의 프로세스, 하나의 스레드만이 접근 가능
2.3.2 실행 순서 동기화

- 특정 조건을 바탕으로 프로세스를 실행하고 일시 중단하기 위해
- 내부적으로
조건 변수(condition variable)를 사용하는데,- 조건 변수는 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수입니다.
- 즉,
wait()와signal()를 호출하는 특별한 함수
💡 조건 변수와 모니터는 별개의 개념
모니터에 진입하기 위해 삽입되는 큐(상호 배제를 위한 큐)와wait가 호출되어 실행이 중단된 프로세스들이 삽입되는 큐(조건 변수에 대한 큐)는 다릅니다.- 전자는 모니터에 한 번에 하나의 프로세스만 진입하도록 하기 위해 만들어진 큐
- 후자는 모니터에 이미 진입한 프로세스의 실행 조건이 만족될 때까지,
- 잠시 실행이 중단되어 기다리기 위해 만들어진 큐
조건 변수로는 wait()와 signal() 연산을 수행할 수 있습니다.
조건변수.wait(): 대기 상태로 변경, 조건 변수에 대한 큐에 삽입조건변수.signal(): wait()으로 대기 상태로 접어든 조건변수를 실행 상태로 변경
(1) wait()
조건변수.wait() : 대기 상태로 변경, 조건 변수에 대한 큐에 삽입

모니터에 진입한 어떤 프로세스가 x.wait( )를 통해 조건 변수 x에 대한 wait를 호출했다고 가정해보겠습니다.
프로세스 B가프로세스 A보다 무조건 먼저 실행되어야 한다고 가정하면,- 프로세스 A에 대해서 조건 변수(임의의 조건변수 x)라고 가정하면,
- x에 대해서 wait()를 호출하면,
- 프로세스A가 조건변수에 대한 큐로 들어가게 되면서, 대기 상태로 접어들게 됩니다.
(2) signal()
조건변수.signal() : wait()으로 대기 상태로 접어든 조건변수를 실행 상태로 변경

- 그리고 그 다음에 있던 B가 실행됩니다.
- 먼저 실행되어야 하는 B가 끝난 다음에,
- siginl()를 호출하면, 조건 변수에 의해서 대기상태로 접어들었던 A가 다시 모니터 안으로 진입해 실행을 재개합니다.
모니터 안에는 하나의 프로세스만이 있을 수 있다.
wait()를 호출했던 프로세스는signal()을 호출한 프로세스가 모니터를 떠난 뒤에 수행을 재개signal()을 호출한 프로세스의 실행을 일시 중단하고,- 자신이 실행한 뒤 다시
signal()을 호출한 프로세스의 수행을 재개
- 자신이 실행한 뒤 다시
💡 실행 순서 동기화정리
- 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는 wait를 통해 실행을 중단한다.
- 특정 프로세스가 실행될 조건이 충족되었을 때에는 signal을 통해 실행을 재개한다.
💡 참고
지금까지 동기화의 개념과 이를 위한 도구를 학습해 보았습니다. C/C++, Java, Python 등의 프로 그래밍 언어를 학습해본 적이 있는 독자라면, 지금까지 학습한 이론이 어떻게 실제 소스 코드로 구현 되는지 심화 학습을 해 보길 권합니다. 아래 링크 synchronization 항목을 참고하기 바랍니다.