관리 메뉴

caLAB

[운영체제 4주차] 하루 30분 컴퓨터 과학 공부하기 본문

개발 공부/컴퓨터 과학

[운영체제 4주차] 하루 30분 컴퓨터 과학 공부하기

도이(doi) 2021. 9. 15. 23:03
728x90

프로세스 동기화

 

프로세스 동기화는 공통적인 부분을 업데이트할 때 결과값이 일정하지 않은 문제가 생기는 것을 해결하기 위해서 필요하다. 프로세스 동기화를 하지 않았을 때의 문제를 은행 계좌를 예시로 설명해보도록 하자.

임계구역(critical section) 문제

위 이미지를 보면 부모는 은행 계좌에 돈을 입금하고 자식은 돈을 출금한다. 만약, 부모와 자식이 동시에 계좌에 접근해서 돈을 입금하고 출금하려고 할 때 어떻게 될까? 이럴 때는 오류가 생기면서 결과값이 일정하지 않게 된다. 이러한 문제를 임계구역(critical section) 문제라고 한다. 임계구역은 공통적으로 접근하고 있는 부분을 말한다. 코드에서 표현하자면 account 변수가 될 것이다. 

 

그렇다면, 이러한 문제를 어떻게 해결할 수 있을까?

해결 방법은 총 3가지 방법이 있다.

 

1. 상호 배타(Mutual Exclusion) -> 오직 한 쓰레드만 진입

2. 진입 결정(Progress) -> 어떤 쓰레드가 먼저 들어갈 것인지 결정

3. 유한대기(Bounded Waiting) -> 유한 시간내에 임계 구역에 들어갈 수 있음

 

위의 해결책은 임계구역 문제를 해결하고, 프로세스의 실행 순서를 제어할 수 있다. 이러한 문제를 해결하는 도구로는 네덜란드 개발자인 Edsger Dijkstra가 제안한 세마포(Semaphores)가 있다.

semaphore의 구조와 함수

세마포는 권한과 관련된 변수(int value)를 하나 가지고 있고, 변수 값을 빼고 프로세스나 쓰레드를 블록하는 acquire함수와 변수 값을 더해서 실행시키는 release 함수가 있다.  

세마포의 구조

위 이미지는 예전에 큐에 대해서 공부했던 친근한 이미지이다. 이 이미지에서 세마포가 어떻게 동작하는지를 보자. 세마포는 cpu가 돌면서 acquire함수에 걸리면 semaphore에 있는 큐에서 대기를 하게 된다. 다시 release함수가 작동하면 큐에서 벗어나서 동작할 수 있다.

 

세마포가 쓰레드를 제어하는 3가지 예시를 보자.

첫 번째는 세마포가 Mutual Exclusion 즉, 상호 배타적 목적으로 사용되는 것이다.

Mutual Exclusion 세마포

세마포의 변수 sem.value를 1로 설정해두고 1번 부모 쓰레드를 돌리고 동시에 자식 쓰레드에 접근하려고 할 때 자식 쓰레드에 있는 sem.acquire 때문에 자식 쓰레드는 부모 쓰레드에서 sem. release를 해주기 전까지는 실행할 수 없고 큐에서 대기를 하게 된다. 부모 쓰레드가 모두 끝나고 sem.release를 실행 해주면, 그 이후에 대기를 타고 있었던 자식 쓰레드가 실행 될 수 있다.

 

두 번째는 세마포가 Ordering 즉, 순서를 제어하는데 사용되는 예시이다.

Ordering 세마포

세마포의 변수 sem.value를 0으로 두고 시작한다. 부모 쓰레드의 시작에는 sem.acquire이 없고, 자식 쓰레드의 시작에는 sem.acquire이 있다면, sem.acqurie이 없는 부모 쓰레드는 실행 가능하지만, 자식 쓰레드는 블록 되어서 세마포 큐에서 대기해야 된다. 부모 쓰레드가 끝난 후 sem.release를 해주면 대기하고 있던 자식 쓰레드를 실행시킬 수 있다. 위와 같이 세마포를 사용하게 되면 부모 프로세스를 먼저 실행시켜줄 수 있다.

 

마지막 예시는 쓰레드가 번갈아가면서 임계구역에 접근하는 것이다.

쓰레드가 번갈아가면서 임계 구역에 접근

세마포 변수가 두 개 필요하다. 한 개는 부모 쓰레드를 체크하는 dsem.value, 나머지 하나는 자식 쓰레드를 체크하는 wsem.value이다. 자식 쓰레드에 acquire함수가 있으면 블록되고, acquire함수가 시작 부분에 없는 부모 쓰레드가 먼저 돌게 된다. 부모 쓰레드가 끝난 후에 자식 쓰레드를 wsem.release해준다 그리고 다시 부모 쓰레드로 돌아가지 않도록, dsem.acquire로 부모 쓰레드에 들어가는 것을 막는다. 자식 쓰레드가 실행되고 다시 부모 쓰레드를 실행하기 위해서 자식 쓰레드가 끝난 후 dsem.release를 해준다. 이렇게 하면 부모 - 자식 - 부모 순으로 번갈아가면서 임계 구역에 접근할 수 있게 된다. 

728x90
반응형
Comments