[프로세스 동기화]
- 동시 다발적으로 실행되는 프로세스들이 서로 협력하여 영향을 주고 받는 과정에서 자원의 일관성을 보장(동기화)해주는 행위
(스레드도 포함 가능)
[동기화(Synchronization)]
- 프로세스들의 수행 시기를 맞추는 것으로 실행 순서를 제어하고, 상호 배제가 있다
실행 순서 제어 - 프로세스를 올바른 순서대로 실행하기
위의 그림에서 Reader 와 Writer 프로세스는 실행의 순서가 있기 때문에 아무렇게나 실행되어선 안된다.
Reader 프로세스는 Book.txt 안에 값이 존재해야 읽을 수 있는 조건이 있기 때문에 이 조건이 만족되어야 실행 가능하기 때문이다.
즉, Write 로 쓰여 있어야 Reader 로 읽을 수 있다는 것이다.
상호 배제 - 동시에 접근해서는 안되는 자원에 하나의 프로세스만 접근하게 하는 것으로 공유가 불가능한 자원의 동시 사용을 피하기 위한 동기화이다.
위의 프로세스 처리 과정에서 동기화가 이루어지지 않을 경우 다음과 같이 구동하게 된다.
17만원이 되어야할 잔액이 15만원이 되는데 이는
프로세스 A 의 잔액 결과가 반영되기 전에 프로세스 B 에서 이전 잔액을 읽고 프로그램이 진행되었기 때문에 최종적으로 프로세스 B 의 결과만 반영된 셈이다.
또 다른 예시를 들자면
위의 결과 역시 동기화가 이루어지지 않았기 때문에(동시에 접근해서는 안되는 자원(총합)에 동시에 접근했기 때문에) 결과값이 달라진다.
동시에 접근해서는 안되는 자원
[공유 자원(Shared resource)]
- 여러 프로세스 혹은 스레드가 동유하는 자원
전역 변수, 파일, 입출력장치, 보조기억장치 등등
[임계 구역(Critical section)]
- 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역(공유 자원에 동시에 접근하는 코드 영역)
레이스 컨디션(race condition) - 임계 구역에 동시에 접근해서 자원의 일관성이 깨지는 상황
(고급 언어로 작성된 한 줄 코드 일지라도 저급 언어에선 한 줄이 아닌 여러 줄일 수 있기 때문에 여러 줄의 저급 언어의 문맥 교환이 발생하면 자원의 일관성이 깨질 수 있다)
[OS 의 임계구역 문제 해결 세 가지 원칙]
상호배제(Mutual exclusion) - 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 들어올 수 없다.
진행(Progress) - 임계 구역에 어떤 프로세스도 진입하지 않았다면 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
유한 대기(Bounded waiting) - 한 프로세스가 임계 구역에 진입하고 싶다면 언젠가는 임계 구역에 들어올 수 있어야 한다.
(임계 구역에 들어오기 위해 무한정 대기해서는 안된다)
[동기화 기법]
뮤텍스 락(MuTex Lock / Mutual Exclusion Lock) - 상호 배제를 위한 동기화 도구(자물쇠 역할)
뮤텍스 락 코드를 간단하게 구현하고자 한다면 다음과 같이 전역 변수 하나와 함수 두 개로도 구현이 가능하다
자물쇠 역할 | 프로세스들이 공유하는 전역 변수 lock |
임계 구역을 잠그는 역할 | acquire 함수 |
임계 구역의 잠금을 해제하는 역할 | release 함수 |
세마포(Semaphore) - 조금 더 일반화된 방식의 동기화 도구로 공유 자원이 여러 개 있는 경우에도 적용이 가능하다.
이진 세마포, 카운팅 세마포가 있다.
-------------------------------------------------------------------------[ 여 담 ]----------------------------------------------------------------------
(이진 세마포 - 흔히들 "이진 세마포 == 뮤텍스 락" 이라고도 하는데, 엄밀히 말해서 둘은 다르다.
둘이 같은 것이라고 부르는 이유는
뮤텍스 락 -> 잠김/해제 상태로 구분함, API 호출 lock/unlock 을 통해서 구현
이진 세마포 -> 0/1 상태로 구분함, API 호출 wait/signal 을 통해서 구현
비슷한 매커님즘으로 구동이 되기 때문에 흔히 뮤텍스 락은 이진 세마포라고한다.
하지만 둘을 구분하는 큰 차이점이 있는데 그게 바로 "소유권" 이다.
뮤텍스 락 -> 락을 소유한 프로세스/스레드 만 뮤텍스를 해제 시킬 수 있음(임계 구역이 안전하게 보호될 수 있도록 보장)
이진 세마포 -> 어떤 스레드나 프로세스라도 세마포를 해제시킬 수 있음(보장 기능이 없음)
이러한 이유로 뮤텍스 락과 이진 세마포가 같다는 것은 "구동" 면에서 같은 것이지, 뮤텍스 락 자체가 이진 세마포라고 볼 수 없다는 것을 이해하고 있어야 한다)
---------------------------------------------------------------------------------------------------------------------------------------------------------
카운팅 세마포 - 임계 구역 앞에서 정지 신호를 받으면 잠시 기다리고, 진행 신호를 받으면 임계 구역에 진입한다.
세마포 코드도 간단하게 구현한다고 한다면 마찬가지로 하나의 전연 변수와 두 개의 함수로 구현이 가능하다.
임계 구역에 진입할수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수) | S 전연 변수 |
임계 구역에 들어가도 기다려야 하는지 알려주는 함수 | wait 함수 |
임계 구역 앞에서 기다리는 프로세스에 진행 신호를 주는 함수 | signal 함수 |
Busy Waiting 으로 발생하는 CPU 사이클 낭비를 해결하는 방법
사용할 수 있는 자원이 없을 경우 = 대기 상태로 만듦 (해당 프로세스의 PCB 를 대기 큐에 삽입)
사용할 수 있는 자원이 생겼을 경우 = 대기 큐의 프로세스를 준비 상태로 만듦(해당 프로세스의 PCB를 대기 큐에서 꺼내 준비 큐에 삽입)
이러한 세마포는 사용자가 입장에서 실수가 발생할 가능성도 높다.
세마포를 누락하거나 wait 과 signal 순서가 바뀌거나 wait 이나 signal 을 중복으로 사용하거나
이러한 경우들이 존재해 사용자 입장에선 다루기엔 다소 쉽지 않은 편이라 아래의 모니터 방식이 등장했다
모니터(Monitor) - 사용자(개발자)가 다루기에 편리한 동기화 도구로 주로 JAVA 에서 사용한다.
- 상호 배제를 위한 동기화 -
인터페이스를 위한 큐공유 자원에 접근하고자 하는 프로세스(인터페이스를 위한)를 큐에 삽입큐에 삽입된 순서대로(한 번에 하나의 프로세스만) 공유 자원 사용
- 실행 순서 제어를 위한 동기화 -
조건 변수(Condition Variable) 이용
프로세스나 스레드의 실행 순서를 제어하리 위해 사용하는 특별한 변수
조건 변수.wait() - 대기 상태로 변경, 조건 변수에 대한 큐에 삽입
조건 변수.signal() - wait() 으로 대기 상태로 접어든 조건 변수를 실행 상태로 변경
특정 프로세스가 아직 실행될 조건인 되지 않았을 때는 wait() 을 통해 실행을 중단한다.
특정 프로세스가 실행될 조건이 충족되었을 때는 signal()을 통해 실행을 재개한다.
모니터 안에는 하나의 프로세스만이 있을 수 있다.
방식 1. wait()를 호출했던 프로세스는 signal()을 호출한 프로세스가 모니터를 떠난 뒤에 수행을 재개
방식 2. signal()을 호출한 프로세스의 실행을 일시 중단하고 자신이 실행한 뒤 다시 signal()을 호출한 프로세스의 수행을 재개
[교착 상태(Dead Lock)]
- 일어나지 않을 사건을 기다리며 진행이 멈춰버리는 현상
교착 상태를 설명하는 대표적인 예시 <식사하는 철학자>
모든 철학자가 동시에 식사를 하려하면 왼쪽 포크를 모두가 들게되어서 오른쪽 포크를 아무도 들 수 없고, 모두가 오른쪽 포크를 들 수 있을 때까지 생각만하고 있기 때문에 아무도 식사를 할 수 없는 상황이 되어버린다.
이 내용을 프로그램적으로 예시를 보게되면 다음과 같다
이러한 교착 상태가 발생했을 때의 상황을 해결하기 위해선 교착상태가 발생했을 때의 상황을 정확히 표현하는 것과, 교착 상태가 일어나는 근본적이 이유를 이해하는 것이 필요하다.
교착 상태가 발생했을 때의 상황을 정확히 표현
[자원 할당 그래프]
- 교착 상태 발생 조건 파악 가능
어떤 프로세스가 어떤 자원을 할당 받아 사용 중인지 확인 가능
어떤 프로세스가 어떤 자원을 기다리고 있는지 확인 가능
종합적으로 아래와 같은 예시를 그려볼 수 있다.
그리고 이것을 활용해 식사하는 철학자 문제를 그려본다면 다음과 같이 그릴 수 있다.
또 위에서 언급한 프로그램적으로 봤을 때의 경우는 다음과 같이 그려볼 수 있다.
위 두가지 예시를 보면 자원 할당 그래프가 '원의 형태'를 띄고 있음을 알 수 있다.
- 교착 상태가 일어나는 근본적인 이유 이해 -
[교착 상태 발생 조건]
상호 배제 - 한 프로세스가 사용하는 자원을 다른 프로세스가 사용할 수 없는 상태
점유와 대기 - 자원을 할당 받은 상태에서 다른 자원을 할당 받기를 기다리는 상태
비선점 - 어떤 프로세스도 다른 프로세스의 자원을 강제로 빼앗지 못하는 상태
원형 대기 - 프로세스들이 원의 형태로 자원을 대기하는 상태
(원의 형태로 이뤄진다고 무조건 교착 상태인건 아니지만, 교착 상태가 발생하면 자원 할당 그래프는 원의 형태를 이루고 있다)
위 네 가지 조건 중 하나라도 만족하지 않으면 교착 상태가 발생하지 않음
== 위 네 가지 조건을 모두 만족하면 교착 상태가 발생할 수 있음
[교착 상태 해결 방법]
교착 상태 예방 - 교착 상태가 발생하지 않도록 교착 상태 발생 조건 중 하나를 없애버린다.
[상호 배제 삭제](모든 자원을 공유 가능하게 만든다) - 이론적으로는 가능할 순 있으나, 현실적인 방법은 아님
[점유와 대기 삭제](특정 프로세스에 자원을 모두 할당하거나, 아예 할당하지 않는 방식으로 배분) - 지금 당장 자원이 필요한 프로세스가 활용되지 못하거나, 할당받지 못해 오랫동안 대기하는 프로세스, 자원을 모두 할당 받은 프로세스가 실제로 자원을 별로 사용하지 않는 경우등이 발생해 자원의 활용률을 낮춰버림
[비선점 삭제(]한 프로세스가 다른 프로세스의 자원을 뺐을 수 있게 만든다) - 선점이 가능한 자원 (CPU 등)에 한해서는 효과적이나 모든 자원이 선점 가능한 것이 아니기 때문에 범용적이지 못함.
[원형 대기 삭제](자원 할당 그래프가 원형이 안되게 구현) - 자원에 번호를 붙이고 오름차순으로 할당
위의 세 가지보단 가장 현실적인 방법이나, 자원에 번호 붙이는 것은 어려운 작업이며 어떤 자원에 어떤 번호를 붙이느냐에 따라 활용률이 달라진다.
교착 상태 회피 - 교착 상태를 무분별한 자원 할당으로 인해 발생했다고 간주해 교착 상태가 발생하지 않을 만큼 조심 조심 할당하여, 배분할 수 있는 자원의 양을 고려하여 교착 상태가 발생하지 않을 만큼만 자원 배분
[안전 순서열] - 교착 상태 없이 안전하게 프로세스들에 자원을 할당할 수 있는 순서
[안전 상태] - 교착 상태 없이 모든 프로세스가 자원을 할당 받고 종료될 수 있는 상태 (안전 순서열이 있는 상태)
[불안전 상태] - 교착 상태가 발생할 수도 있는 상태 (안전 순서열이 없는 상태)
안전 상태에서 안전 상태로 움직이는 경우에만 자원을 할당하는 방식으로 항시 안전 상태를 유지하도록 자원을 할당한다.
ex) 은행원 알고리즘
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
교착 상태 검출 후 회복
[선점을 통한 회복] - 교착 상태가 해결될 때까지 한 프로세스씩 자원을 몰아주는 방식
[프로세스 강제 종료를 통한 회복]
- 교착 상태에 놓인 프로세스 모두 강제 종료(작업 내역을 잃을 위험 존재) 하거나
교착 상태가 해결될 때까지 한 프로세스씩 강제 종료(해결 여부를 계속 확인하기 때문에 오버헤드 발생 위험 존재)
교착 상태 무시
타조 알고리즘 - 교착 상태가 발생할 가능성이 매우 낮거나 발생히도 큰 문제가 되지 않는 경우에 이를 탐지하거나 해결하지 않고 무시하는 방식
(타조는 위험이 생겼을 때 땅속에 머리를 묻는다는 속설에 유래된 용어(실제로 타조는 머리를 낮출 뿐 묻진 않는다))