컴퓨터 구조 & 운영체제/운영체제

[운영체제] 프로세스와 스레드의 동기화 기법(뮤텍스 락, 세마포, 모니터)

ReBugs 2023. 7. 3.

이 글은 혼자 공부하는 컴퓨터 구조 + 운영체제 (저자 : 강민철)의 책과 유튜브 영상을 참고하여 개인적으로 정리하는 글임을 알립니다.


동시다발적으로 실행되는 프로세스와 스레드들은 공동의 목적을 수행하기 위해 서로 협력하며 영향을 주고받기도 한다.

이렇게 협력하여 실행되는 프로세스와 스레드들은 실행 순서와 자원의 일관성을 보장해야 하기에 반드시 동기화가 되어야 한다.

프로세스 동기화란 프로세스 사이에 동기화를 하는 것을 말한다. 현재는 대부분 스레드 기준으로 문맥 교환이 일어나기 때문에 스레드 동기화라고도 불린다.

이 글에선 동기화 기법의 전체적 개념을 다루므로 프로세스 동기화와 스레드 동기화의 개념을 구분 짓지 않았으면 좋겠다.

프로세스뿐만 아니라 스레드도 동기화 대상이다.
정확히 말하면 실행의 흐름을 갖는 모든 것은 동기화의 대상이다.
이 글에서는 대부분의 전공서 표현에 따라 프로세스 동기화라고 칭하겠다.

 

2023.07.02 - [컴퓨터 구조 & 운영체제] - [운영체제] 프로세스와 스레드의 동기화 개념

 

[운영체제] 프로세스와 스레드의 동기화 개념

이 글은 혼자 공부하는 컴퓨터 구조 + 운영체제 (저자 : 강민철)의 책과 유튜브 영상을 참고하여 개인적으로 정리하는 글임을 알립니다. 동시다발적으로 실행되는 프로세스와 스레드들은 공동의

rebugs.tistory.com

 

 

뮤텍스 락(Mutex lock:MUTual EXclusion lock)

임계 구역 문제와 이를 해결하기 위한 동기화를 옷 가게에서 탈의실을 이용하는 것에 비유해 보자.

탈의실에는 한 명만 들어갈 수 있다. 손님들은 탈의실이라는 자원을 이용하고 탈의실 안에는 손님 한 명 씩만 들어올 수 있으니, 손님은 프로세스, 탈의실은 임계 구역이라고 할 수 있다.

만약 밖에서 탈의실에 사람이 있는지 없는지 알 수 없는 상황이라면, 일단 탈의실을 열어 보고 자물쇠가 걸려있다면 탈의실에 사람이 있다고 판단한다.

이 자물쇠 기능을 코드로 구현한 것이 뮤텍스 락이다.

  • 동시에 접근해서 안 되는 자원에 동시에 접근하지 않도록 만드는 도구
  • 즉, 상호 배제를 위한 동기화 도구

임계 구역에 진입하는 프로세스는 자신이 지금 임계 구역에 있음을 알리기 위해 뮤텍스 락을 이용해 임계 구역에 자물쇠를 걸어둘 수 있고, 다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨 있지 않다면 임계 구역에 진입할 수 있다.

 

뮤텍스 락은 전역 변수와 두 개의 함수로 구현할 수 있다.

  • 자물쇠 역할 : 프로세스들이 공유하는 전역 변수 lock
  • 임계 구역을 잠그는 역할 : acquire 함수
  • 임계 구역의 잠금을 해제하는 역할 : release 함수

 

acquire 함수

  • 프로세스가 임계 구역에 진입하기 전에 호출하는 함수
  • 임계 구역이 잠겨 있다면 임계 구역이 열릴 때까지(lock이 false가 될 때까지) 임계 구역을 반복적으로 확인
  • 임계 구역이 열려 있다면 임계 구역을 잠그는(lock을 true로 바꾸는) 함수

 

release 함수

  • 임계 구역에서의 작업이 끝나고 호출하는 함수
  • 현재 잠긴 임계 구역을 열어주는(lock을 false로 바꾸는) 함수

 

acquire와 release 함수를 아래와 같이 임계 구역 전후로 호출함으로써 하나의 프로세스만 임계 구역에 진입할 수 있다.

 

이렇게 되면 프로세스는 

  • 임계 구역에 진입할 수 없다면, 무작정 기다리고,
  • 임계 구역에 진입할 수 있다면, 임계 구역을 잠근 뒤 임계 구역에서 작업을 진행하고,
  • 임계 구역에서 빠져나올 때엔 다시 임계 구역의 잠금을 해제함으로써

임계 구역을 보호할 수 있다.

프로그래밍 언어에서의 뮤텍스 락
C/C++, python 등의 일부 프로그래밍 언어에서는 사용자가 직접 acquire, release 함수를 구현하지 않도록 뮤텍스 락 기능을 제공한다.
실제 프로그래밍 언어가 제공하는 뮤텍스 락은 더욱 정교하게 설계되어 있다.

 

바쁜 대기(busy wait)
acquire 함수를 보면
임계 구역이 잠겨 있을 경우 프로세스는 반복적으로 lock를 확인하는 것을 볼 수 있다.
이런 대기 방식을 바쁜 대기라고 한다.

 


 

세마포(semaphore)

  • 뮤텍스 락보다 좀 더 일반화된 방식의 동기화 기법
  • 공유 자원이 여러 개 있는 경우에도 적용 가능
세마포의 종류
이진 세마포와 카운팅 세마포 등이 있다.
이진 세마포는 뮤텍스 락과 비슷한 개념.
이 글에서는 여러 공유 자원을 다룰 수 있는 카운팅 세마포를 다룬다.

 

세마포는 철도 신호기에서 유래한 단어이다.

기차는 신호기가 내려가 있을 때는 '멈춤' 신호로 간주하고 잠시 멈춘다.

반대로 신호기가 올라와 있을 때는 '가도 좋다'는 신호로 간주하여 다시 움직인다.

세마포는 이와 같이 임계 구역을 관리한다.

 

세마포는 하나의 변수와 두 개의 함수로 단순하게 구현할 수 있다.

  • 전역 변수 S : 임계 구역에 진입할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수)를 나타냄
  • wait 함수 : 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려줌
  • signal 함수 : 임계 구역 앞에서 기다리는 프로세스에 '이제 가도 좋다'라고 신호를 줌
세마포 변수와 함수 이름
일부 전공서에는 wait와 signal 함수를 P, V로 명명하기도 하고 down, up으로 명명하기도  한다.

 

뮤텍스 락과 마찬가지로 세마포도 임계 구역 진입 전후로 wait()와 signal()을 호출한다.

 

wait 함수

 

signal 함수

 

busy wait를 극복하지 못한 세마포 예시

예를 들어 P1, P2, P3 세 개의 프로세스가 두 개의 공유자원에 순서대로 접근한다고 가정하면, 공유 자원이 두 개 있으니 변수 S는 2가 된다.

 

바쁜 대기(busy wait)의 단점 극복(상호 배제를 위한 동기화 기법)

wait 함수는 사용할 수 있는 공유 자원이 없는 경우 프로세스는 무작정 무한히 반복하며 S를 확인해야 한다.

이는 CPU 사이클을 낭비한다는 점에서 손해이다.

그래서 세마포는 아래와 같은 방법으로 이를 해결한다.

  • 사용할 수 있는 자원이 없을 경우 대기 상태로 만듦(해당 프로세스의 PCB를 대기 큐에 삽입)
  • 사용할 수 있는 자원이 생겼을 경우 대기 큐의 프로세스를 준비 상태로 만듦(해당 프로세스의 PCB를 대기 큐에서 꺼내 준비 큐에 삽입)

이를 간단한 코드로 나타내면 아래와 같다.

 

busy wait을 극복한 세마포 예시 (상호 배제를 위한 동기화 기법)

예를 들어 P1, P2, P3 세 개의 프로세스가 두 개의 공유자원에 순서대로 접근한다고 가정하면, 공유 자원이 두 개 있으니 변수 S는 2가 된다.

 

세마포를 활용한 실행 순서 동기화

  1. 세마포의 변수 S를 0으로 초기화
  2. 먼저 실행할 프로세스 뒤에 signal 함수 호출
  3. 다음에 실행할 프로세스 앞에 wait함수 호출

P1이 먼저 실행되면 P1이 임계 구역에 먼저 진입한다, P2가 먼저 실행되더라도 P2는 wait 함수를 만나므로 P1이 먼저 임계 구역에 진입한다.

그리고 P1이 임계 구역의 실행을 끝내고 signal을 호출하면 이후에 P2가 임계구역에 진입한다.

즉, P1이 먼저 실행되는 P2가 먼저 실행되는 반드시 P1, P2 순서대로 실행된다.

세마포도 뮤텍스 락과 마찬가지로 많은 프로그래밍 언어에서 제공한다.

 


 

모니터(mornitor)

세마포는 아래와 같은 단점을 가지고 있다.

  • 매번 임계 구역 앞뒤로 일일이 wait와 signal 함수를 호출해야 한다.
  • 세마포를 누락하거나, wait와 signal함수 호출 순서를 헷갈린 경우, 중복해서 사용한 경우 예기치 못한 결과를 얻을 수 있다.

 

이에 최근에 등장한 동기화 기법이 모니터이다.

모니터는 세마포에 비하면 개발자가 사용하기에 훨씬 편리한 도구이다.

 

상호 배제를 위한 동기화

  • 객체지향 프로그래밍에서 사용되는 방법
  • 인터페이스를 위한 큐
  • 공유 자원에 접근하고자 하는 프로세스를 (인터페이스를 위한) 큐에 삽입
  • 큐에 삽입된 순서대로 (한 번에 하나의 프로세스만) 공유 자원 이용
  • 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근

모니터는 위의 그림처럼 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어서 관리한다.

모니터를 통해 공유 자원에 접근하고자 하는 프로세스를 큐에 삽입하고, 큐에 삽입된 순서대로 하나씩 공유자원을 이용하도록 한다.

즉, 모니터에 진입하기 위한 큐를 만들고,  모니터 안에 항상 하나의 프로세스만 들어오도록 하여 상호 배제를 위한 동기화를 제공한다.

 

실행 순서 제어를 위한 동기화

  • 조건 변수(condition variable) 이용하여 실행 순서 제어
  • 조건 변수와 모니터는 별개의 개념
조건 변수
프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수
wait()와 signal()을 호출할 수 있는 특별한 변수

  • 조건변수.wait() : 호출한 프로세스의 상태를 대기 상태로 전환하고 일시적으로 조건 변수에 대한 대기 큐에 삽입하는 연산
  • 조건변수.signal() : wait()를 호출하여 대기큐에 삽입된 프로세스를 실행 상태로 변경하는 연산
  • 상호 배제를 위한 큐 : 모니터에 진입하기 위해 삽입되는 큐(모니터에 한 번에 하나의 프로세스만 진입하도록 하기 위해 만들어진 큐)
  • 조건 변수에 대한 큐 : wait가 호출되어 실행 중단된 프로세스들이 삽입되는 큐(이미 진입한 프로세스의 실행 조건이 만족될 때까지 잠시 실행이 중단되어 기다리기 위해 만들어진 큐)

즉, 조건 변수에 대한 큐는 상호 배제를 위한 큐와는 별개의 큐이다.

 

프로세스 B가 모니터를 떠난 뒤 프로세스 A가 실행하도록 하는 방법(방법 1)

예를 들어 프로세스 A와 B가 있는데 A가 먼저 큐에 있지만 A보다 B가 먼저 실행되어야 한다고 가정하자.

A가 x.wait()를 통해 조건 변수 x에 대한 wait를 호출했다고 하면

그 프로세스는 아래의 그림처럼 조건 변수 x에 대한 큐에 삽입되므로 모니터는 다시 비게 된다.

A는 대기상태이므로 다음 차례인 B가 실행이 된다.

wait() 으로 인해 일시 중지된 프로세스는 다른 프로세스의 signal()를 통해 실행이 재개될 수 있기에

실행을 마친 프로세스 B가 x.signal()을 통해 조건 변수 x에 대한 signal을 호출했다고 하면 이를 통해 조건 변수 x에 대해 대기 상태에 있던 프로세스가 깨어나 모니터 안으로 다시 들어올 수 있다.

 

프로세스 B를 일시정지 시 뒤 프로세스 A가 실행하도록 하는 방법

아래의 부분은 이해가 잘되지 않아서 맞는 건지 잘 모르겠다.

예를 들어 프로세스 A와 B가 있는데 A가 먼저 큐에 있지만 A보다 B가 먼저 실행되어야 한다고 가정하자.

  1. A가 먼저 인터페이스에 들어간다.
  2. A는 signal을 통해 B의 실행시킨 뒤 자신을 일시 중지(wait)한다.
  3. B가 실행이 끝난다.
  4. 다시 A의 실행이 재개된다. 

댓글