lleesla 2024. 6. 8. 11:57

1. 동기화란

동기화의 의미

  • 협력적으로 실행되는 프로세스들이 아무렇게나 동시에 실행되서는 안되기 때문에 동기화가 필요하다.
  • 정보・통신 분야에서의 동기화 : 작업들 사이의 수행 시기를 맞추는 것

프로세스들 사이의 수행 시기를 맞추는 것은 무엇을 의미하는가?

  • 실행 순서 제어 : 프로세스를 올바른 순서대로 진행하기
  • 상호 배제 : 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
첫번째, 실행 순서 제어를 위한 동기화

 

Writer라는 프로세스와 Reader라는 프로세스가 동시에 실행 중이라고 가정하고, Writer 프로세스는 Book.txt 파일에 값을 저장하는 프로세스이고, Reader 프로세스는 Book.txt 파일에 저장된 값을 읽어 들이는 프로세스라고 가정해보자

  • 이때 두 프로세스가 무작정 순서없이 실행되어서는 안된다.
  • Reader는 Writer가 실행을 끝내야 실행할 수 있기 때문이다.
  • 즉, Reader 프로세스는 ‘Book.txt 안에 값이 존재한다’라는 전제 하에 실행할 수 있는 것이다.
두번째, 상호 배제를 위한 동기화

상호 배제는 공유가 불가능한 자원의 동시 사용을 피하기 위한 알고리즘이다. 계좌에 10만 원이 저축되어 있고, A는 현재 저축된 금액에 2만 원을 추가로 넣는 프로세스, B는 현재 저축된 금액에 5만 원을 넣는 프로세스라고 가정해보자

프로세스 A 실행 과정

  1. 계좌의 잔액을 읽어 들인다.
  2. 읽어 들인 잔액에 2만 원을 더한다.
  3. 더한 값을 저장한다.

프로세스 B 실행 과정

  1. 계좌의 잔액을 읽어 들인다.
  2. 읽어 들인 잔액에 5만 원을 더한다.
  3. 더한 값을 저장한다.

프로세스 A, B가 동시 실행되었을 때 당연히 17만원이 있을 것이라고 생각할 것이다. 하지만 동기화가 제대로 이루어지지 않으면 아래와 같은 경우가 발생 할 수 있다.

 

왜 이러한 상황이 발생할까?

A와 B가 ‘잔액’이라는 데이터를 동시에 사용하는데, A의 실행이 끝나기도 전에 B가 잔액을 읽어버렸기 때문이다. 올바르게 실행하기 위해서는 같이 한 프로세스가 잔액에 접근했을 때 다른 프로세스는 기다려야한다.

  • 이렇게 동시에 접근해서는 안 되는 자원에 동시에 접근하지 못하게 하는 것이 상호 배제를 위한 동기화이다.

생산자와 소비자 문제

  • 생산자와 소비자는 물건을 계속해서 생산하는 프로세스인 생산자와 물건을 계속해서 소비하는 프로세스인 소비자로 이루어져 있다.
  • 이 프로세스들은 ‘총합’이라는 데이터를 공유하고 있다.
  • 생산자는 버퍼에 물건을 넣은 후, 물건의 총합에 해당하는 변수를 1 증가시키고, 소비자는 버퍼에서 물건을 빼낸 후 물건의 총합에 해당하는 변수를 1 감소시킨다.
  • 물건이 처음에 10개 있었다고 가정하고 생산자를 100,000번, 소비자를 100,000번 동시에 실행해보자
총합 = 10

생산자() {
	버퍼에 데이터 삽입
	'총합' 변수 1 증가
}

소비자() {
	버퍼에서 데이터 빼내기
	'총합' 변수 1 감소
}

겉으로는 문제가 없어보인다. 하지만 아래와 같이 막상 생산자와 소비자를 동시에 실행해 보면 결과가 10이 아닌 다른 수가 되거나 실행 중에 오류가 나기도 한다.

 

초기 합계 = 10
생산자, 소비자 실행 이후 합계 : 63078

 

이는 프로세스가 제대로 동기화 되지 않아 발생한 문제이다. 생산자가 소비자의 작업이 끝나기도 전에 총합을 수정하여 엉뚱한 결과가 발생한 것이다.


공유 자원과 임계 구역

동시에 접근해서는 안되는 자원은 뭘까 ?

생산자와 소비자 프로세스에서는 ‘총합’, ‘잔액’이라는 공동의 자원을 두고 작업을 진행 했는데, 이러한 자원을 공유 자원이라고 한다.

공유 자원은 전역 변수가 될 수도 있고, 파일이 될 수도 있고, 입출력 장치, 보조기억장치가 될 수도 있다.

또한 공유 자원 중에는 두 개 이상의 프로세스를 동시에 실행하면 문제가 발생하는 자원이 있다. ‘잔액’ 변수, ‘총합’ 변수가 여기에 해당한다.

동시에 실행 했을 때 문제가 발생하는 자원에 접근하는 코드 영역을 임계 구역이라고 부른다.

  • 두 개 이상의 프로세스가 임계 구역에 진입하고자 하면 둘 중 하나는 대기해야한다.
  • 먼저 진입한 프로세스의 작업이 마무리 되어야 그 다음 프로세스가 임계 구역에 진입하게 된다.

레이스 컨디션

  • 임계 구역은 두 개 이상의 프로세스가 동시에 실행되면 안 되는 영역이지만, 잘못된 실행으로 인해 여러 프로세스가 동시에 임계 구역의 코드를 실행하여 문제가 발생하는 경우를 말한다.
  • 레이스 컨디션 발생 시 계좌 잔액 문제나 생산자와 소비자 문제처럼 데이터의 일관성이 깨지는 문제가 발생한다.
  • 컴퓨터는 고급 언어가 아닌 저급 언어를 실행하기 때문에 여러 줄의 저급 언어로 변환되는데 변환된 고급 언어 한 줄을 실행하는 과정에서 문맥 교환이 일어날 수 있다. 따라서 저급 언어를 실행하는 과정에서 문맥 교환이 일어난다면 결과에 문제가 발생하게 된다.
  • 이 때 상호 배제를 위한 동기화가 두 개 이상의 프로세스가 임계 구역에 동시에 접근하지 못하도록 관리해준다.

 

  • 운영체제는 임계 구역 문제를 세 가지 원칙 하에 해결한다.
    • 상호 배제 : 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 임계 구역에 들어올 수 없다.
    • 진행 : 임계 구역에 어떤 프로세스도 진입하지 않았다면 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
    • 유한 대기 : 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다.(무한정 대기 X)

2. 동기화 기법

뮤텍스 락

옷 가게에서 마음에 드는 옷이 있으면 손님은 탈의실에 들어가서 옷을 입어볼 수 있다. 이 때 탈의실에는 한 명만 들어갈 수 있다. 손님들은 탈의실이라는 자원을 이용하고 탈의실 안에는 손님 한 명씩만 들어올 수 있기 때문에, **손님은 ‘프로세스’, 탈의실은 ‘임계 구역’**이라고 할 수 있다.

임계구역 안에 프로세스가 진행 중인지 어떻게 알 수 있을까?

  • 탈의실과 손님을 예로 들었을 때 탈의실을 열어보고 자물쇠가 걸려있으면 손님이 안에 있다고 판단하고 기다린다.
  • 이와 같이 자물쇠 기능을 코드로 구현한 것이 뮤텍스 락이다.
  • 뮤텍스 락은 동시에 접근해서는 안 되는 자원에 동시에 접근하지 않도록 만드는 도구, 상호 배제를 위한 동기화 도구이다.
    • 임계 구역에 진입하는 프로세스는 ‘내가 지금 임계 구역에 있음’을 알리기 위해 뮤텍스 락을 이용하여 임계 구역에 락을 걸어둘 수 있다.
    • 다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨 있지 않다면 임계 구역에 진입할 수 있다.

뮤텍스 락의 단순한 형태는 하나의 전역 변수와 두 개의 함수로 구현할 수 있다.

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

acquire 함수

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

release 함수

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

세마포

뮤텍스 락의 경우 하나의 공유 자원에 접근하는 프로세스를 상정한 방식이다. 즉 탈의식이 하나 있는 경우를 가정한 것이다. 하지만 탈의실이 여러 개가 있는 상황처럼 공유 자원이 여러 개 있을 때 여러 개의 프로세스가 각각 공유 자원에 접근할 수 있어야한다. 예를 들어 옷가게에 탈의실이 세 개 있다고 가정 했을 때 하나의 탈의실에는 한 사람만 들어갈 수 있지만 이 경우에는 세 명이 동시에 탈의실을 이용할 수 있다.

이처럼 공유 자원이 여러 개 있는 상황에서도 적용이 가능한 동기화 도구를 세마포라고 한다. 세마포는 ‘멈춤’ 신호와 ‘ 가도 좋다’는 신호로 임계 구역을 관리한다.

 

세마포는 어떻게 구현 될까?

  • 임계 구역에 진입할 수 있는 프로세스의 개수를 나타내는 전역 변수 S
  • 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는 wait 함수
  • 임계 구역 앞에서 기다리는 프로세스에 ‘이제 가도 좋다’고 신호를 주는 signal 함수
wait()

// 임계 구역

signal()

임계 구역 진입 전후로 wait(), signal()을 호출

 

wait 함수는 어떻게 만들까 ?

wait () {
	while (S <= 0) /* 1 */
	;              /* 2 */
	S--;           /* 3 */
}
  1. 임계 구역에 진입할 수 있는 프로세스 개수가 0 이하라면
  2. 사용할 수 있는 자원이 있는지 반복적으로 확인하고,
  3. 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S(임계 구역에 진입할 수 있는 프로세스의 개수)를 1 감소시키고 임계 구역에 진입한다.

signal 함수는 어떻게 만들까?

signal () {
	S++;         /* 1 */
}
  1. 임계 구역에서의 작업을 마친 뒤 S를 1 증가시킨다.

사용할 수 있는 공유 자원이 없는 경우 ?

  • 사용할 수 있는 공유 자원이 없는 경우엔 프로세스는 무한히 반복하며 임계 구역에 진입할 수 있는 프로세스의 개수를 확인해야 한다.
  • 이렇게 바쁜 대기를 반복하며 확인할 시간에 CPU는 더 생산성 있는 작업을 할 수 있다.
  • 따라서 세마포는 더 좋은 방법을 사용한다.
    • wait 함수는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태를 대기 상태로 만들고, 그 프로세스의 PCB를 세마포를 위한 대기 큐에 집어넣는다.
    • 다른 프로세스가 임계 구역에서의 작업이 끝나고 signal 함수를 호출하면 signal 함수는 대기 중인 프로세스를 대기 큐에서 제거하고, 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨준다.
wait() { 
	S--;
	if (S < 0) {
		add this process to Queue; /* 1 */
		sleep();                   /* 2 */
	}
}
  1. 해당 프로세스 PCB를 대기 큐에 삽입한다.
  2. 대기 상태로 접어든다.

 

signal() {
	S++;
	if(S <= 0) {
		remove a process p from Queue;
		wakeup(p);
	}
}
  1. 대기 큐에 있는 프로세스 p를 제거한다.
  2. 프로세스 p를 대기 상태에서 준비 상태로 만든다.

모니터

세마포의 잘못된 사용

  1. 세마포를 아예 누락한 경우
  2. wait과 signal 순서를 헷갈린 경우
  3. wait과 signal을 중복해서 사용한 경우

코드가 방대해지고 복잡해지면 위와 같은 상황은 얼마든지 발생할 수 있다.

이에 최근에 등장한 동기화 도구가 모니터이다. 모니터는 세마포에 비해 사용자가 사용하기에 훨씬 편리한 도구이다.

상호 배제를 위한 동기화

  • 모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스를 묶어 관리한다.
  • 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근하도록 한다.
  • 모니터를 통해 공유 자원에 접근하고자 하는 프로세스를 큐에 삽입하고, 큐에 삽입된 순서대로 하나씩 공유 자원을 이용하도록한다.
  • 즉, 모니터는 공유 자원을 다루는 인터페이스에 접근하기 위한 큐를 만들고, 모니터 안에 항상 하나의 프로세스만 들어오도록 한다.

 

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

  • 특정 조건을 바탕으로 프로세스를 실행하고 중단하기 위해 조건 변수를 사용하는데, 조건 변수는 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수이다.
  • 조건 변수로 wait과 signal 연산을 수행할 수 있다.
    • 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는 wait을 통해 실행을 중단한다.
    • 특정 프로세스가 실행될 조건이 충족되었을 때에는 signal을 통해 실행을 재개한다.
  • 상호 배제를 위한 큐는 모니터에 한 번에 하나의 프로세스만 진입하도록 만들어진 큐이고, 조건 변수에 대한 큐는 모니터에 이미 진입한 프로세스의 실행 조건이 만족될 때까지 잠시 실행이 중단되어 기다리기 위한 큐이다.