12. 프로세스 동기화
1. 동기화란?
동시다발적으로 실행되는 프로세스들은 공동의 목적을 올바르게 수행하기 위해 서로 협력하며 영향을 주고 받기도 한다. 이렇게 협력하여 실행되는 프로세스들은 실행 순서와 자원의 일관성을 보장해야 하기 때문에, "동기화(synchronization)" 되어야 한다.
1.1 동기화의 의미
동시다발적으로 실행되는 많은 프로세스는 서로 데이터를 주고받으며 협력하며 실행될 수 있다. 예를 들어, 워드 프로그램 내에서 돌아가는 각각의 프로세스들은 각기 다른 독립적인 프로세스지만 공동의 목표를 위해 서로 협력하는 존재이다.
이렇게 협력적으로 실행되는 프로세스들은 아무렇게나 마구 동시에 실행되어서는 안된다. 올바른 실행을 위해서는 "동기화"가 필수적이다.
"프로세스 동기화"란 프로레스들 사이의 수행 시기를 맞추는 것을 의미한다. 크게 아래 두 가지를 일컫는다.
실행 순서 제어 : 프로세스를 올바른 순서대로 실행하기
상호 배체 : 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
실행 순서 제어 동기화
가령 write 프로세스와 reader 프로세스가 실행중이라고 가정해보자. 이 두 프로세스는 무작정 아무 순서대로 실행되어서는 안된다. reader 프로세스는 writer 프로세스 실행이 끝나야 비로소 실행할 수 있기 때문이다. 이렇게 동시에 실행되는 프로세스를 올바른 순서대로 실행하는 것이 "실행 순서 제어 동기화"이다.
상호 배제 동기화
"상호 배제" 는 공유가 불가능한 자원의 동시 사용을 피하기 위해서 사용하는 알고리즘이다. 은행 앱에서 2개의 출금이 있다고 했을 때 2개의 출금이 동시에 잔액에 접근하면 무시무시한 버그가 발생할 수 있다.
자바에서는
synchronized를 통해서 간단하게 제어
1.2 생산자 소비자 문제
상호 배제를 위한 동기화에 대해 조금만 더 알아보면, 이와 관련된 고전적이고 유명한 문제로 "생산자 소비자 문제" 가 있다. 생산자 소비자 문제는 물건을 계속해서 생산하는 프로세스인 생산자와 물건을 계속해서 소비하는 프로세스인 소비자로 이루어져 있다.
생산자와 소비자는 "총합" 이라는 데이터를 공유하고 있다. 생산자는 버퍼에 물건을 넣은 후, 물건의 총합에 해당하는 변수를 1 증가시키고, 소비자는 버퍼에 물건을 빼낸 후 물건의 총합에 해당하는 변수 1을 감소시킨다.
이 상황은 크게 문제가 없어 보이지만, 막상 생산자와 소비자를 동시에 실행해 보면 예상치 못한 결과를 맞이할 수 있다.
이는 생산자 프로세스와 소비자 프로세스가 제대로 동기화 되지 않았기 때문에, 발생한 문제이다. 생산자와 소비자가 동시에 사용하면 안되는 영역을 "임계 영역(critical section)" 이라고 부르는데, 이 영역을 동시에 접근했기 때문에 문제가 발생한다.
1.3 공유 자원과 임계 구역
그렇다면 동시에 접근해서는 안되는 자원이란 무엇일까?
계좌 잔액 문제와 생산자 소비자 문제의 예시에서 동시에 실행되는 프로세스들은 전역 변수 '잔액', '총합' 이라는 공동의 자원을 두고 작업을 했다. 이러한 자원을 "공유 자원(shared resource)" 라고 한다. 공유 자원은 전역 변수가 될 수도 있고, 파일이 될 수도 있고, 입출력장치, 보조기억장치가 될 수도 있다.
그리고 이 공유 자원 중에는 두 개 이상의 프로세스를 동시에 실행하면 문제가 발생하는 자원이 있다. 이렇게 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역을 "임계 영역(critical section)" 이라고 한다.
임계 구역은 두 개 이상의 프로세스가 동시에 실행하면 안 되는 영역이지만, 잘못된 실행으로 인해 여러 프로세스가 동시 다발적으로 임계 구역의 코드를 실행하여 문제가 발생하는 경우가 있다. 이를 "레이스 컨디션(race condition)" 이라고 한다. 레이스 컨디션 문제가 발생하면 계좌 잔액 문제나 생산자 소비자 문제처럼 데이터의 일관성이 깨지는 문제가 발생한다.
이때 상호 배제를 위한 동기화는 이와 같은 문제가 발생하지 않도록 두 개 이상의 프로세스가 임계 구역에 동시에 접근하지 못하도록 관리하는 것을 의미한다.
운영체제는 이러한 임계 구역 문제를 아래 세 가지 원칙 하에 해결한다. 달리 말하면 상호 배제를 위한 동기화를 위해서는 아래 세가지 원칙을 반드시 지켜야만 한다.
상호 배제(mutual exclusion) : 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 임계 구역에 들어올 수 없다.
진행(progress) : 임계 구역에 어떤 프로세스도 진입하지 않았다면 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
유한 대기(bounded waiting) : 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다.
2. 동기화 기법
2.1 뮤텍스 락
임계 영역 문제와 이를 해결하기 위한 동기화를 옷 가게에서 탈의실을 이용하는 것에 비유해보자. 옷 가게 탈의실은 한명만 이용할 수 있기에 임계 영역으로 부를 수 있다.
만일 밖에서 탈의실에 사람이 있는지 없는지 알 수 없는 상황이라면 어떻게 탈의실이 이용중임을 알 수 있나? 일단 탈의실을 열어 보고 자물쇠가 걸려 있다면 탈의실 안에 사람이 있다고 판단하고 기다린다. 자물쇠가 걸려 있지 않다면 탈의실을 이용하면 되는 것이다.
이 자물쇠 기능을 코드로 구현한 것이 "뮤텍스 락(mutex lock)" 이다. 뮤텍스 락은 동시에 접근해서는 안 되는 자원에 동시에 접근하지 않도록 만드는 도구, 다시 말해 상호 배체를 위한 동기화 도구이다.
임계 구역에 진입하는 프로세스는 '내가 지금 임계 구역에 있음' 을 알리기 위해 뮤텍스 락을 이용해 임계 구역에 자물쇠를 걸어둘 수 있고, 다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨 있지 않다면 임계 구역에 진입할 수 있다.
뮤텍스 락의 매우 단순한 형태는 하나의 전역 변수와 두 개의 함수로 구현될 수 있다.
자물쇠 역할 : 프로세스들이 공유하는 전역 변수
lock임계 구역을 잠그는 역할 :
acquire함수참고로 acquire 함수는 임계 구역이 잠겨 있는 경우 프로세스는 반복적으로 lock 을 확인하는 것을 알 수 있다. 이런 대기 방식을 "바쁜 대기(busy wait)" 라고 한다.
임계 구역의 잠금을 해제하는 역할 :
release함수
2.2 세마포어
"세마포어(semaphore)" 는 뮤텍스 락과 비슷하지만, 조금 더 일반화된 방식의 동기화이다. 뮤텍스 락은 하나의 공유 자원에 접근하는 프로세스를 상정한 방식이다. 즉 탈의실이 하나 있는 경우를 가정하고 만든 동기화 도구이다. 하지만 탈의실이 여러 개 있는 상황처럼 공유 자원이 여러 개 있을 경우(각 공유 자원에는 하나의 프로세스만 진입이 가능할지라도) 여러 개의 프로세스가 각각 자원에 접근이 가능해야 한다.
이처럼 세마포어는 공유 자원이 여러 개 있는 상황에서도 적용이 가능한 동기화 도구이다.
세마포어는 철도 신호기에서 유래한 단어이다.
기차는 신호기가 내려가 있을 때는 '멈춤' 신호로 간주하고 잠시 멈춘다.
반대로 신호기가 올라와 있을 때는 '가도 좋다'는 신호로 간주하고 다시 움직이기 시작한다.
세마포어는 이와 같이 '멈춤' 신호와 '가도 좋다' 는 신호로서 임계 구역을 관리한다. 즉, 프로세스는 임계 구역 앞에서 멈춤 신호를 받으면 잠시 기다리고, 가도 좋다는 신호를 받으면 그제서야 임계 구역으로 들어가게 된다.
세마포어가 어떻게 구현되는지 간략하게 살펴보자. 세마포어는 뮤텍스 락과 비슷하게 하나의 변수와 두개의 함수로 단순하게 구현할 수 있다.
임계 구역에 접근할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수) 를 나타내는 전역 변수
S임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는
wait함수임계 구역 앞에서 기다리는 프로세스에 '이제 가도 좋다'고 신호를 주는
signal함수
여기서 한 가지 문제가 발생한다. 이는 앞서 설명한 뮤텍스 락에서 해당되는 문제인데, 사용할 수 있는 공유 자원이 없는 경우 프로세스는 무작정 무한히 반복해서 S 를 확인해야 한다. 이렇게 바쁜 대기를 반복하며 확인할 시간에 CPU 는 더 생산성이 있는 작업을 할 수 있을 텐데, CPU 주기를 낭비한다는 점에서 손해이다.
그래서 실제로 세마포어는 더 좋은 방법을 사용한다. wait 함수는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태를 대기 상태로 만들고, 그 프로세스의 PCB 를 세마포어를 위한 대기큐에 집어 넣는다. 그리고 다른 프로세스가 임계 구역에서의 작업이 끝나고, signal 함수를 호출하면 signal 함수는 대기 중인 프로세스를 대기 큐에서 제거하고, 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨준다.
Last updated