[혼공컴운] Ch12. 프로세스 동기화
강민철 - 혼자 공부하는 컴퓨터구조 운영체제
책을 읽으며 개인적으로 정리한 내용입니다.
목차
- 동기화란
- 동기화의 의미
- 생산자와 소비자 문제
- 공유 자원과 임계 구역
- 동기화기법
- 뮤텍스 락
- 세마포
- 모니터
✅ 동기화란
프로세스는 동시에 실행되면서도, 서로 협력하며 영향을 준다.
이때 실행 순서와 자원의 일관성을 보장해야해서 "동기화"되어야한다.
⏺ 동기화의 의미
프로세스들 사이의 수행 시기를 맞추는 것
1) 실행 순서 제어 - 프로세스를 올바른 순서대로 실행하기
만약 Writer와 Reader 프로세스가 동시에 실행되고 있다.
이때 Book.txt 에 Writer가 어떤값을 저장하려 하는데 만약 Reader가 먼저 읽어버리면 안된다.
Book.txt에 값이 존재한다 는 조건이 만족되어야만 Reader는 실행을 이어나갈 수 있다.
> 동시에 실행되는 프로세스들을 올바른 순서대로 실행해야한다.
2) 상호 배제 : 동시에 접근하면 안되는 자원에, 하나의 프로세스만 접근하게 하기
계좌에 10만원이 있다.
A는 현재 금액 + 2만원을 넣는 프로세스이고,
B는 현재 금액 + 5만원을 넣는 프로세스이다.
A
1) 계좌를 잔액을 읽는다
2) 계좌잔액 + 20000
3) 더해진 값을 저장한다.
B
1) 계좌를 잔액을 읽는다
2) 계좌잔액 + 50000
3) 더해진 값을 저장한다.
A와 B가 동시에 실행되었다고 가정해보자.
여기서 동기화가 제대로 이뤄지지 않으면
<동기화가 잘못된 경우>
A | B | 현재 잔액 |
잔액을 읽는다 | 10만원 | |
계좌 잔액 + 20000 | 10만원 | |
문맥교환 | 10만원 | |
잔액을 읽는다 | 10만원 | |
계좌 잔액 + 50000 | 10만원 | |
문맥교환 | 10만원 | |
더한 값 저장 | 12만원 | |
더한 값 저장 | 15만원 .....ㅠㅠ |
A가 끝나서 현재잔액에 값이 저장되기 전에
B가 현재 잔액을 읽고 값을 마지막에 저장했기 떄문에
결과가 원하는대로 나오지 않는다.
<동기화가 잘 된 경우>
A | B | 현재 잔액 |
잔액을 읽는다 | 10만원 | |
계좌 잔액 + 20000 | 10만원 | |
문맥교환 | 10만원 | |
더한 값 저장 | 12만원 | |
잔액을 읽는다 | 12만원 | |
계좌 잔액 + 50000 | 12만원 | |
문맥교환 | 12만원 | |
더한 값 저장 | 17만원 |
⏺ 생산자와 소비자 문제
생산자 : 물건을 계속해서 생산하는 프로세스
소비자 : 물건을 계속해서 소비하는 소비자
* 생산자와 소비자가 <총합>이라는 데이터를 공유하고 이다.
총합 = 10
<생산자> {
버퍼에 데이터를 넣는다.
'총합'에 1을 더한다.
}
<소비자> {
버퍼에서 데이터를 뺀다.
'총합'에 1을 뺀다.
}
생산자와 소비자가 10만번 동시에 실행된다고 했을 때
코드상엔 문제가 없어보이고, 총합 변수는 초기값인 10에 계속 머무르는것을 예상할 수 잇지만..
두 스레드 실행 이후의 합이 63078 이 된다던지,,, -13750이 된다던지 하는 결과값을 확인할 수 있다.
* 이는 제대로 동기화 되지 않아서 발생한 문제이다.
소비자가 생산자의 작업이 끝나기 전에 총합을 수정하고
생산자가 소비자의 작업이 끝나기 전에 총합을 수정했기 때문!
⏺ 공유 자원과 임계 구역
공유자원 : 동시에 실행되는 프로세스들이 공통된 자원에 접근해 작업을 할 때의 , 자원
>> 변수, 파일, 장치 등
임계구역 : 공유자원에 접근하는 코드 영역 (실행시 문제가 발생함)
즉 2개 이상의 프로세스가 임계 구역에 진입하고자 할때
둘 중 하나는 대기해야한다.
잘못된 실행으로 여러 프로세스가 동시에 임계구역의 코드를 실행하게 되면 "레이스 컨디션"이 발생한다고 한다.
> 데이터 일관성이 깨진다.
그럼 임계 구역 문제는 어떻게 해결하는가?
동기화를 위해서 아래 3가지 원칙을 반드시 지켜져야한다.
1. 상호 배제 : 한 프로세스가 임계구역에 있을 때 다른 프로세스는 들어올 수 없다.
2. 진행 : 임계 구역에 아무 프로세스도 진입하지 않았다면, 진입하고자 하는 프로세스는 들어갈 수 있어야한다.
3. 유한 대기 : 임계 구역에 진입하고자 할 때 그 프로세스는 언젠가는 들어갈 수 있어야한다 (무한정 대기하면 안된다)
✅ 동기화 기법
프로세스의 동기화는 어떻게 이루어질까?
임계구역에 하나의 프로세스만 진입하게 하고, 실행순서를 보장하는 방법은 무엇이 있을까?
⏺ 뮤텍스 락
자물쇠를 걸 듯 코드로 구현된 것
동시에 접근해서는 안되는 자원에 , 동시에 접근하지 않도록 만드는 도구
- 임계 구역에 진입하는 프로세스는 "내가 지금 임계구역에 있다" 를 알리기 위해 뮤텍스락을 사용하여 자물쇠를 걸어둔다.
- 다른 프로세스는 임계구역이 잠겨있으면 기다리고, 잠겨있지 않을때 임계 구역에 진입할 수 있다.
1) 프로세스들이 공유하는 전역 변수 lock
2) 임계 구역을 잠그는 역할 : acquire 함수 == 프로세스가 임계 구역에 진입하기 전에 호출하는 함수
- lock 이 잠겨있다면 false가 될 때까지 확인한다.
- lock이 false일 때 임계 구역을 잠그기 위해 lock을 true로 바꾼다.
3) 임계 구역의 잠금을 해제하는 역할 : release 함수
- lock을 false로 바꾼다.
프로세스는 임계 구역 전후에 메서드들을 호출함으로써,
하나의 프로세스만을 임계구역으로 진입하도록 보장할 수 있다.
acquire()
// 임계 구역 작업
release()
* 바쁜 대기
acquire에서 프로세스가 반복적으로 임계구역이 사용가능한지 확인하는 것
⏺ 세마포어
뮤텍스 락과 유사, 조금더 일반화된 동기화 도구
* 뮤텍스 락은 하나의 공유 자원에 접근하는 프로세스를 상정한 방식이다.
* 공유자원이 여러개 있을 경우엔 세마포어를 사용한다.
- 임계 구역에 진입할 수 있는 프로세스의 개수 or 사용가능한 공유자원의 개수 - S (전역변수)
- 임계 구역에 들어가도 되는지 기다려야하는지 - wait 함수
- 임계 구역 앞에서 기다리는 프로세스에게 , 가도 좋다고 신호를 주는 - signal 함수
세마포어도 임계 구역 진입 전후로 wait과 signal을 사용한다.
✍️ wait
wait함수에서 사용가능한 공유자원의 개수가 0개 이하이면 : 반복적으로 사용가능한 자원이 존재하는지 확인한다.
wait함수에서 사용가능한 공유자원의 개수가 1개 이상이면 : S를 감소시키고 임계 구역에 진입한다.
✍️ signal
작업이 끝났으므로 S를 1 증가시킨다.
Example
- 프로세스가 P1, P2, P3이 존재한다.
- 공유자원이 2개 존재한다
- 공유 자원에 P1, P2, P3 순서대로 접근환다.
프로세스 P1,2,3의 과정
wait()
//임계 구역
signal()
- P1 - wait 호출, S는 2에서 1로 감소, 임계구역에 진입한다.
- P2 - wait 호출, S는 1에서 0으로 감소, 임계 구역에 진입한다.
- P3 - wait 호출, S는 0이므로 무한히 반복하며 S를 확인한다.
- P1 - 임계구역의 작업 종료, signal()호출, S는 1 증가시킨다.
- P3 - S가 1이 되었음을 확인했다. S를 1 감소시키고 임계 구역에 진입한다.
문제점
* 사용할 수 있는 공유자원이 없으면 프로세스는 무한정 반복하며 S를 확인해야한다.
* CPU 주기를 낭비하게 되므로 손해이다.
해결 😃
->> 실제로는 세마포어가 다른 더 좋은 방법을 사용한다!
[과정]
wait에서 사용가능한 자원이 없을 때 -> 프로세스 상태를 대기 상태로 만든다.
프로세스의 PCB를 세마포어를 위한 대기큐에 집어 넣는다.
- 다른 프로세스는 임계구역에서의 작업이 끝나고, signal() 호출하면
- signal 함수는 대기중인 프로세스를 대기큐에서 제거한다.
- 이후 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮긴다.
wait() {
S--;
if(S<0) {
add this process to Queue; //PCB를 대기큐에 삽입한다.
sleep(); ///대기상태가 된다
}
}
signal() {
S++;
if(S<=0) {
remove a process p from queue; //대기큐에 있는 프로세스 p를 제거한다.
wakeup(p); // 프로세스 p를 대기상태에서 준비상태로 만든다.
}
}
Example
- 프로세스가 P1, P2, P3이 존재한다.
- 공유자원이 2개 존재한다
- 공유 자원에 P1, P2, P3 순서대로 접근환다.
프로세스 P1,2,3의 과정
wait()
//임계 구역
signal()
- P1 - wait 호출, S는 2에서 1로 감소, 임계구역에 진입한다.
- P2 - wait 호출, S는 1에서 0으로 감소, 임계 구역에 진입한다.
- P3 - wait 호출, S는 0에서 -1로 감소, S == -1 이므로 PCB를 대기큐에 넣고, 대기상태로 전환시킨다.
- P1 - 임계구역의 작업 종료, signal()호출, S는 1 증가시킨다. S는 0이므로 대기 상태였던 P3을 대기큐에서 꺼내 준비큐로 옮긴다.
- 깨어난 프로세스 P3은 임계 구역에 진입한다.
- P2 - 임계구역의 작업 종료, signal()호출, S는 1 증가시킨다 -> 1
- P3 - 임계구역의 작업 종료, signal()호출, S는 1 증가시킨다 -> 2
세마포어를 이용한 프로세스 실행 순서의 제어
1) S = 0으로 둔다.
2) 먼저 실행할 프로세스 뒤에 signal 함수를 둔다.
3) 다음에 실행할 프로세스 앞에 wait함수를 붙힌다.
이렇게 되면
- P1이 임계 구역에 먼저 진입하고, P2가 먼저 실행되더라도 P2는 wait()을 만나므로 S가 1 감소해 임계구역에 들어가지 않고, 대기큐로 들어가게 된다. (그러면 P1이 임계구역에 들어가게 된다)
- P1이 임계구역에서의 작업이 끝나면, signal()을 호출하게 되며 이제야 P2가 임계구역으로 들어가게된다.
결국 P1, P2 어떤 프로세스가 먼저 실행되든, P1 -> P2의 실행순서를 보장 할 수 있게 된다!
⏺ 모니터
세마포어를 통한 동기화는 훌륭하지만, 사용하기 불편하다.
매번 임계구역에 앞뒤로 wait과 signal을 붙혀야하기 떄문이다.
그래서 모니터라는 동기화 도구가 생겼고,
세마포어에 비하면 사용하기 훨씬 편리하다.
1) 공유자원과 , 자원에 접근하기 위한 인터페이스를 묶어 관리한다.
2) 프로세스는 반드시 인터페이스를 통해서만 공유자원에 접근할 수 있다.
3) 모니터를 이용해 공유자원에 접근하고자 하는 프로세스를 큐에 삽입한다.
4) 큐에 삽입된 순서대로 하나씩 공유자원을 이용하도록 한다.
모니터는 인터페이스에 접근하기 위한 큐를 만들고, 모니터안에 항상 하나의 프로세스만 들어오도록 한다.
모니터는 조건변수를 사용한다.
특정 조건이 되었을 때 프로세스를 실행하고 일시 중단한다.
> 프로세스/스레드의 실행 순서를 제어하기 위해 사용한다.
조건변수에 대한 큐는 , 모니터에 이미 진입했으나, 프로세스의 실행조건이 만족될때까지 잠시 실행이 중단되어 기다리기 위해 만들어진 큐이다.
어떤 프로세스가 wait호출로 조건변수에 대한 큐로 삽입되게 되면, 모니터 안은 다시 비게 되면서 다른 프로세스가 모니터 안으로 들어올 수 있게 된다.
wait으로 일시중지된 프로세스는 다른 프로세스의 signal연산으로 실행을 재개할 숭 씨다.
x.signal을 통해 조건 변수x에 대한 signal을 호출하면, 대기상태에 있던 프로세스가 깨어나 모니터 안으로 다시 들어온다.
모니터는 조건변수를 통해 실행순서 제어를 위한 동기화를 제공한다는 점을 기억하자.
1) 특정 프로세스가 아직 실행될 조건이 되지 않았으면 wait로 실행을 중단한다.
2) 특정 프로세스가 실행될 조건이 충족되었을 때엔 signal로 실행을 재개한다.
references
혼자 공부하는 컴퓨터구조 운영체제 (강민철)