지난 시간에 스레드에 대해서 알아봤었죠?!
그리고 멀티 스레드 환경에서는 서로 공유하는 영역이 있기 때문에 같은 데이터에 접근하는 경우
문제가 발생할 수 있다고 설명드렸어요~
그래서 제가 "동기화 작업"이 필요하다고 했었죠??
자! 그럼 동기화가 뭔데?! 한번 들어가봅시다 :)
저번 시간에 예시를 들면서 설명 잠깐 했어요
스레드 "배씨"가 공유자원인 도화지에 사자의 얼굴을 그리다가 다른 스레드 "이씨"가 들어와서 호랑이로 바꿨잖아요?? 그렇게 된다면 스레드 "배씨" 가 계획했던 목표가 바뀌면서 나아갈 수 없는 상황이 되겠죠.
이처럼 프로세스/스레드는 같은 데이터에 접근해야하는 경우가 있어요!
이때 일정한 규칙없이 데이터 수정을 허용하게 된다면 데이터의 신뢰성이 사라지겠죠??
그렇기때문에 데이터의 일관성을 유지하기 위해 처리하는 방법이 필요한 거죠.
실행 중인 스레드가 공유 데이터 영역에서 완전히 종료된 후 다음 스레드가 동작할 수 있도록 구현하는 것이
"동기화 작업" 이라고 생각하시면 돼요!
추가적으로 "임계구역(Critical Section)에 대해서도 알아봅시다!!
임계구역은 공통으로 접근 하여 데이터를 수정할 수 있는 코드영역이에요.
저번 시간에 예시로 들었던 도화지가 있는 영역이 임계 구역이라고 보시면됩니다~
이러한 임계 구역에서 처리를 제대로 해주지 않는다면 여러개의 프로세스/스레드가 동시에 접근할 때,
작업 순서에 따라 최종값이 달라지면서 데이터의 일관성이 사라지게 돼요.
이러한 현상을 "경쟁 상황(Race Conditon)" 이라고 부릅니다.
이 문제가 발생하지 않기 위한 3가지 조건이 있어요
상호 배제 (Mutual Exclusion) |
진행 (Progress) |
유한 대기(Bounded Waiting) |
쉽게 설명드리기 위해 화장실이 하나밖에 없는 술집이 있다고 생각해봅시다! (다들 소주 좋아하세요? 전 알쓰랍니다 ^^)
여기서 임계구역은 화장실이고 줄 서있는 사람들을 프로세스/스레드라고 생각해봐요!
여기서 프로세스/스레드를 프로세스라고만 부르겠습니다.
상호배제는 한 프로세스가 임계 영역에 들어오면 다른 프로세스가 들어오면 안돼요!
예를 들어, 알쓰 "배씨"가 하나밖에 없는 화장실에서 볼일을 보고 있는데 술찌 "이씨"가 급하다고 문을 벌컥 열어서 "너 나와!!" 이러는거죠! (그런 몰상식적인 행동을 해선 안됩니다 ㅡ.ㅡ)
진행은 임계영역에서 실행되는 프로세스가 없는 상황에서 임계영역에 들어가고 싶어하는 프로세스가 있다면
그 프로세스가 즉시 들어갈 수 있어야해요.
예를 들어, 술찌 "이씨"가 화장실이 비었는데 맨 앞에서 바보같이 계속 기다리고 있으면 들어가게 해야죠ㅎㅎ
유한 대기는 다른 프로세스의 기아 현상을 방지하기 위해서 한번 임계 구역에 들어간 프로세스는 다음에 임계구역에 들어갈 때 제한을 두어야합니다. (기아 현상은 임계 영역에 들어가기 위해 무한정 기다리는 현상이에요!)
예를 들어, 알쓰 "배씨"가 화장실에 나왔는데 다시 바로 들어가면 안되죠. 볼일을 보고 나왔으면 다음 차례에 대기 하는 사람이 들어가게끔 해줘야 하는거에요. 즉, 모든 사람들이 유한 시간이내에 화장실을 사용할 수 있게끔 말이죠.
자! 이제 경쟁상황을 피하기 위한 구체적인 해결방법을 알아봅시다!
크게 뮤텍스와 세마포어가 있어요!
세마포어는 다음 포스팅에서 다룰 예정이니 뮤텍스에 대해서 먼저 알아볼게요 !
뮤텍스를 간단하게 설명드리자면 공유자원이 하나일 때 처리하는 동기화 방법입니다~
Lock과 Unlock을 사용합니다.
lock : 현재 임계 구역에 들어갈 권한을 얻어옴 ( 만약 다른 프로세스/스레드가 임계 구역 수행 중이면 종료할 때까지 대기 )
unlock : 현재 임계 구역을 모두 사용했음을 알림. ( 대기 중인 다른 프로세스/스레드가 임계 구역에 진입할 수 있음 )
예를 들어볼게요! 식당에 화장실이 하나밖에 없고 화장실 열쇠도 하나라고 가정해봅시다!
1. 화장실에 처음 들어간 사람이 열쇠를 들고 들어가서 문을 잠궈요.
2. 다음 사람이 들어갈려고 하는데 문이 잠겨있고 열쇠가 없으니까 못들어가요.
3. 이후 화장실 안에 있던 사람이 볼일을 다 보고 나오면 열쇠를 반납해요.
조금 이해되시나요??
스레드가 하나의 공유자원을 사용하고 있을 때 Lock 을 걸어주어 다른 스레드의 접근을 막으며 해당 스레드가 공유자원을 다 사용하고 나올 때는 Unlock 을 해주면서 다음 스레드가 들어올 수 있게끔 해줍니다.
뮤텍스 기법으로는 데커(Dekker) 알고리즘, 피터슨(Peterson) 알고리즘, 제과점(Backery) 알고리즘이 있습니다.
하나하나 살펴볼게요. 지금부터 코드가 들어갈테니까 마음의 준비를 해두세요 ㅎㅎ
그리고 코드마다 아래에 더보기를 만들어놓을테니 구체적인 설명을 보고싶으신 분은 펼쳐서 보세요!
1. 데커(Dekker) 알고리즘
flag와 turn 변수를 통해 임계 구역에 들어갈 프로세스/스레드를 결정하는 방식이에요
- flag는 프로세스 중 누가 임계영역에 진입할 것인지 나타내는 변수에요.
- turn은 누가 임계 구역에 들어갈 차례인지 나타내는 변수에요.
while (true) {
flag[i] = true; //프로세스 i가 임계 구역에 진입을 시도해요.
while (flag[j]) {//프로세스 j가 현재 임계 구역에 있는지 확인
if (turn == j) {//j가 임계 구역 사용 중이라면
flag[i] = false; //프로세스 i의 진입을 취소한다.
while (turn == j); //turn 이 j에서 변경될 때까지 대기.
flag[i] = true; //j turn 이 끝나면 i가 다시 진입 시도.
}
}
}
//임계 구역
turn = j; // 임계 구역 사용이 끝나면 turn을 넘김.
flag[i] = flase; //flag 값을 false로 바꿔서 임계 구역 사용 완료를 알림.
여기서 Pi는 프로세스 i , Pj는 프로세스 j라고 생각해볼게요!
Pi가 임계 구역에 진입을 시도하려고 할 때 먼저 Pj 라는 놈이 임계 구역에 있는지 또는 임계구역에 들어가려고 하는지 확인해요!
만약 Pj가 임계 구역에 있거나 임계 구역에 들어갈 차례면 Pi의 진입을 취소 하고 Pj가 임계 구역을 빠져나갈 때까지 기다리다가 Pj가 빠져나가면 Pi를 임계 구역에 진입시키는거죠.
만약 Pj가 임계 구역에 있지 않고 들어가려 하지도 않으면 Pi가 임계영역으로 진입을 합니다.
이후 Pi가 임계 구역에서 작업을 처리하고 나서 나올 때는 Pj 에게 기회를 양보하기 위해 turn = j 로 표시하는 거죠.
2. 피터슨(Peterson) 알고리즘
데커 알고리즘과 유사하지만 상대방 프로세스/스레드에게 진입 기회를 양보하는 것에 차이가 있어요!
while (true) {
flag[i] = true; //프로세스 i가 임계 구역에 진입을 시도.
turn = j; //다른 프로세스에게 진입 기회 양보
while (flag[j] && turn == j) {
//다른 프로세스가 진입 시도하면 대기
}
}
//임계영역
flag[i] = false;
// flag값을 flase로 바꿔 임계 구역 사용 완료를 알림
Pi 가 임계영역에 진입하려면 먼저 flag[i] = true 로 하여 임계 영역에 들어가고 싶다는 의사 표시를 해줘요!
그리고 Pi가 아닌 다른 프로세스가 임계구역에 들어갈려고 했었는지 체크하기 위해 turn = j 라는 코드를 써줍니다.
즉, Pi가 임계구역에서 공유자원을 쓰기 전에 먼저 쓰고 싶어했던 애가 있는지 확인하고 수행시켜주는 코드인거죠.
만약 Pj가 임계구역에 들어갈려고 했었다면 Pj를 공유자원을 할당하고 Pj 가 자원을 다 사용 후 Pi 차례가 온다면 임계구역에 들어와서 작업을 합니다.
작업이 끝나면 Pi가 작업이 완료되면 flag[i] = false 로 설정해주면서 Pi 가 임계 구역에서의 작업이 끝났다는 것을 알려주는 거에요
3. 제과점(Bakery) 알고리즘
다음은 제과점 알고리즘이에요!
빵집을 비유로 고객들에게 번호표를 부여하여, 번호표를 기준으로 먼저 처리해야할 일들의 우선순위를 부여하는 알고리즘이에요~ 해당 알고리즘은 여러 프로세스/스레드에 대한 처리가 가능하고 가장 작은 수의 번호표를 가지고 있는 프로세스가 임계 구역에 진입한답니다!
while(true) {
isReady[i] = true; // 번호표 받을 준비
number[i] = max(number[0~n-1]) + 1; // 현재 실행 중인 프로세스 중에 가장 큰 번호 배정
isReady[i] = false; // 번호표 수령 완료
for(j = 0; j < n; j++) { // 모든 프로세스 번호표 비교
while(isReady[j]); // 비교 프로세스가 번호표 받을 때까지 대기
while(number[j] && number[j] < number[i] && j < i);
// 프로세스 j가 번호표 가지고 있어야 함
// 프로세스 j의 번호표 < 프로세스 i의 번호표
}
}
// ------- 임계 구역 ---------
number[i] = 0; // 임계 구역 사용 종료
먼저 isReady[i] 에서 대기번호 number를 부여받는 중임을 다른 프로세스에게 알리고 번호표를 할당 받습니다.
그리고 번호표 수령을 완료했다고 표시로 isReady[i] = false로 설정한 후에 현재 번호표를 부여받는 스레드가 없는지 체크하고 있으면 기다려줍니다.
이후 번호표를 발급 받았고 번호표가 자신보다 작은 스레드가 있는지 체크해줍니다.
하지만 베이커리 알고리즘에서는 두개의 스레드가 똑같은 번호를 받지 않을 것이란 걸 보장하지 못해요.
이 경우 낮은 이름을 갖고 있는 스레드가 먼저 처리돼요. 즉, 대기번호는 같을지라도 프로세스 id가
더 낮은 프로세스가 먼저 생성된 거라고 말할 수 있기에 먼저 처리되는 거에요.
그리고 다른 프로세스의 number값이 자기보다 크거나, 같더라도 자신의 id가 더 낮을 때까지 기다리죠.
이렇게 기다리다가 조건을 만족하게 되면 임계구역에 진입해 작업을 하고 모든 작업이 완료되면 해당 프로세스를
0으로 처리합니다.
이번 포스팅에서는 뮤텍스에 대해서 알아보았는데요!
다음 시간에는 뮤텍스와 비슷하지만 조금 다른 세마포어에 대해서 알아볼려고 합니다!! 감사합니다ㅎㅎ :)
'남이 읽는 CS > 운영체제' 카테고리의 다른 글
[운영체제 6편] 유저모드 커널모드 (3) | 2022.04.03 |
---|---|
[운영체제 5편] 데드락이 무엇인가 (4) | 2022.03.22 |
[운영체제 4편] 세마포어가 무엇인가 (5) | 2022.03.15 |
[운영체제 2편] 스레드란 무엇인가 (4) | 2022.03.05 |
[운영체제 1편] 프로세스란 무엇인가 (4) | 2022.03.04 |
댓글