Araina’s Blog

4. 병행 프로세스와 상호 배제 본문

Develop Study/Operating System

4. 병행 프로세스와 상호 배제

Araina 2021. 10. 12. 23:36


 

 

1. 병행 프로세스

프로세스 여러 개가 동시에 실행되는 것을 병행 프로세스라고 부른다.

병행 프로세스는 독립 프로세스협력 프로세스로 나눠진다.

병행 프로세스들은 제한된 자원을 공유하기 위해 상호작용해야 한다.

또한 프로세스 간의 동기화 문제교착 상태 문제를 해결하기 위한 방안이 필요하다.

병행 프로세스는 다음과 같은 환경에서 실행된다.

  • 다중 프로세서 시스템
  • 분산 처리 시스템
  • 단일 프로세서 시스템에서의 다중 프로그래밍

 

1.1) 독립 프로세스

다른 프로세스와 영향을 주고받지 않으므로, 동작을 재현할 수 있다.

초기값에 따라 동일한 결과를 보여준다.

 

1.2) 협력 프로세스

다른 프로세스와 영향을 주고받으면서 기능을 수행한다.

 

1.3) 병렬성과 병행성

병렬 컴퓨팅병행 프로세스는 여러 프로세스들을 동시에 실행한다는 점에서 공통점이 있다.

둘의 차이점은 다음과 같다.

  • 병렬 컴퓨팅: 동일한 순간에 별도의 프로세서에서 각각 실행한다. 다중 프로세스 시스템에서만 사용할 수 있다.
  • 병행 프로세스: 프로세스들이 동시에 실행되지만, 한 순간에 여러 프로세스를 실행할 필요는 없다. 단일 프로세서 시스템과 다중 프로세서 시스템 모두 사용할 수 있다.

 

1.4) 선행 제약

프로그램을 구성하는 문장 간의 실행 순서에 대한 제약을 선행 제약이라고 부른다.

문장 S1, S2, ~, Sn가 있을 때, 선행 제약은 Si < Sk로 표시한다.

이는 Si가 선행되어야 Sk를 실행할 수 있다는 의미이다.

만약 S1 < S2이고, S2 < S3라면, S1 < S3가 성립한다.

즉, S1이 실행되려면 S3와 S2가 차례대로 선행되어야 한다는 것이다.

두 문장 간에 선행 제약이 없으면 이들은 독립적이므로, 병행 실행이 가능하다.

 


 

2. 선행 그래프와 병행 프로그램

2.1) 선행 그래프

선행 제약을 비순환 그래프로 표현한 것을 선행 그래프라고 부른다.

각 문장이 그래프의 정점(노드)에 해당된다.

S1 노드에서 S2 노드로 가는 간선은 문장 S1이 완전히 수행된 다음에 문장 S2가 실행됨을 의미한다.

다중 프로세서 시스템에서 선행 그래프를 구성하면 여러 문장이 동시에 수행되므로, 총 수행 시간을 줄일 수 있다.

위와 같이 수행 순서를 정의할 수 없어 모순이 발생하는 순환 그래프를 순환 선행 그래프라고 한다.

 

2.2) 병행 프로그램 작성을 위한 언어구조

선행 그래프는 연산 부분의 선행 제약을 정의하는 데 유용하나, 프로그램에는 사용하기가 어렵다.

이러한 단점을 극복하고자, 프로그램의 여러 문장의 선행 관계 명시를 위한 다양한 언어구조가 제시되었다.

  • fork와 join 구조
  • 병행 문장 (parbegin, parend)

 

2.3) fork와 join 구조

병행을 최초로 언어적 표현으로 명시한 구조.

fork 명령어는 프로그램에서 2개의 병행 수행을 정의한다.

join 명령어는 여러 개의 병행 수행을 하나로 결합하는 방법을 제공한다.

결합할 병행 수행의 수를 명시하는 변수를 가진다.

fork, join 구조 예시는 다음과 같다.

 

2.4) 병행 문장 (parbegin, parend)

프로세스 1개가 여러 가닥의 병행 수행 흐름으로 퍼졌다가 다시 한 가닥으로 뭉쳐지는 것을 나타내는 고급 언어 구조.

각각의 Si는 단일 문장(명령어)이다.

parbegin과 parend 사이에 있는 모든 문장들은 병행 수행이 가능하다.

복잡한 구조의 병행 문장과 선행 그래프 예시는 다음과 같다.

 


 

3. 상호 배제와 동기화

3.1) 상호 배제

한 순간에 1개의 프로세스만 사용할 수 있는 공유 자원에 대해, 1개의 프로세스가 공유 자원을 사용하는 동안 다른 프로세스가 그 자원에 접근할 수 없게 하는 것.

순차적으로 재사용이 가능한 자원을 공유하는 프로세스들이 공유 자원을 동시에 사용하지 못하게 실행을 제어하기 위해서는 프로세스 동기화가 필요하다. (상호 배제를 위한 프로세스 동기화)

프로세스들이 충돌 없는 연산(ex. 데이터 읽기)을 수행하는 경우, 공유 자원에 동시 접근할 수 있다.

 

3.2) 생산자 - 소비자 프로세스

여러 개의 비동기적 프로세스들이 작업 수행을 위해 서로 협동하고, 병행 수행하는 대표적인 예시.

생산자 프로세스는 소비자 프로세스가 소비하는 정보를 생산한다.

상호 배제와 동기화가 필요하다.

생산자의 데이터 생산, 소비자의 데이터 소비는 서로 독립적이므로 공유 버퍼가 필요하다.

생산자와 소비자는 같은 버퍼를 사용하므로, 공유 변수에 동시 접근할 때 문제가 발생할 수 있다.

생산자가 이미 채워진 버퍼에 데이터를 채우려고 시도하거나, 소비자가 빈 버퍼에 데이터를 꺼내고자 할 때 문제가 발생한다.

이러한 문제를 해결하기 위해서는 유한 버퍼를 구현해야 한다.

 

3.3) 유한 버퍼

  • 독립 프로세스: 다른 프로세스와 영향을 주고받지 않으므로, 동작을 재현할 수 있다. 초기값에 따라 동일한 결과를 보여준다.
  • 협력 프로세스: 다른 프로세스와 영향을 주고받으면서 기능을 수행한다.

유한 버퍼는 2개의 논리적 포인터 in과 out을 가지는 순환 배열이다. (큐)

데이터가 소비/생산될 때마다 버퍼를 관리하는 in, out 포인터 위치가 달라지므로, 중간에 값이 소비되었음을 감지하기 위해 수식 연산이 필요하다.

  • in = (in + 1) % size
  • out = (out + 1) % size

 

3.4) 생산자 - 소비자 문제

생산자 - 유한 버퍼 - 소비자

필요에 따라 생산자는 in 변수, 소비자는 out 변수에 접근할 수 있다.

또한 생산자와 소비자 모두 counter 변수에 접근할 수 있다.

생산자는 새로운 원소를 생산, 지역 변수 nextProduced에 저장한다.

버퍼가 가득 차 있다면 대기 상태에 빠지고, 저장할 공간이 있으면 값을 저장(in), 공유 변수 counter 값을 1 증가시킨다.

소비자는 소비할 원소를 지역 변수 nextConsumed에 저장한다.

버퍼가 비어 있다면 대기 상태에 빠지고, 저장된 값이 있으면 값을 읽고(out), 공유 변수 counter 값을 1 감소시킨다.

만약 생산자와 소비자 프로세스가 동시에 counter 공유 변수에 접근한다면, counter에 잘못된 값이 들어갈 수도 있다.

counter에 5가 저장되어 있을 때, 생산자가 값을 1 증가시키고, 소비자가 값을 1 감소시키면, 접근 순서에 따라 counter 변수 값이 4, 5, 6 중에 1개가 되어 버린다.

이는 counter++ 또는 counter--는 프로그래밍 코드에서는 1줄로 보이겠지만, 기계어 코드로 변환하면 3줄 분량의 코드이기 때문이다.

이 때문에 기계어 처리 순서가 상황에 따라 뒤죽박죽으로 섞일 수 있기 때문에 counter 변수의 값이 무작위성을 가지게 된다.

즉, 프로세서가 1개라면, 생산자, 소비자의 동시 수행은 개념적으로만 동시에 작동(시분할)하는 것이다.

그러므로 생산자 계산 처리를 먼저 완료한 뒤, 소비자 계산 처리를 진행한다는 보장이 없다.

이처럼 여러 프로세스가 동시에 공유 데이터에 접근할 때, 접근 순서에 따라 실행 결과가 달라지는 상황을 경쟁 상태라고 부른다.

경쟁 상태를 방지하기 위해서는 병행 프로세스들을 동기화해야 한다.

경쟁 상태는 단일 프로세서 시스템에서도 발생할 수 있다.

 

3.5) 임계 영역

다수의 프로세스가 실행 가능한 영역이면서 한 순간에 1개의 프로세스만 실행할 수 있는 영역이다.

공유 자원의 독점을 보장하기 위한 코드 영역으로, 위의 코드 상에서 counter 변수 값을 변경하는 코드가 임계 영역에 해당한다.

프로세스가 공유 데이터(임계 자원)에 접근하는 동안, 그 프로세스는 임계 영역에 있다고 표현한다.

 

3.6) 임계 영역 문제 해결 방법

임계 영역 내에서 인터럽트 발생을 방지함으로써 임계 영역 문제를 해결할 수 있지만, 이는 단일 프로세서 환경에서만 유효하다.

멀티 프로세서 환경에서도 임계 영역 문제를 해결하기 위해서는 임계 영역에 대한 프로세스 동기화를 진행해야 한다.

즉, 한 프로세스가 임계 영역 내에 있을 경우, 다른 프로세스는 임계 영역에 진입할 수 없도록 해야 한다.

임계 영역 문제 해결책은 다음과 같은 조건을 만족해야 한다.

  • 상호 배제: 1개의 프로세스가 임계 영역에서 수행 중일 때, 다른 프로세스는 임계 영역에서 수행할 수 없어야 한다.
  • 진행: 임계 영역을 실행하는 프로세스가 없고 일부 프로세스가 임계 영역에 들어가려고 할 때, 나머지 영역을 실행 중이지 않은 프로세스들만 임계 영역에 들어갈 수 있는 대상이 되어야 한다. 즉, 나머지 영역을 실행 중인 프로세스가 다른 프로세스의 임계 영역 진입을 방해해서는 안된다.
  • 한정 대기: 1개의 프로세스가 임계 영역 진입 요청을 수락받을 때까지 다른 프로세스가 임계 영역에 진입하는 횟수가 제한되어야 한다.

또한 임계 영역 문제 해결책은 다음과 같은 조건을 가정한다.

  • 각 기계어 명령어들은 분할할 수 없게 실행된다. 즉, 기계어 명령어를 실행하면, 인터럽트 영향 없이 해당 명령어를 끝까지 실행한다.
  • 각각의 프로세스들은 nonzero speed(작업 중간에 멈춤이 없음)로 실행한다고 가정한다.
  • 프로세스들의 상대적인 속도에 대해서는 어떤 가정도 하지 않는다.

 

3.7) 임계 영역에 대한 프로세스 동기화

1번에 1개의 프로세스만 임계 영역에 들어가도록 동기화하는 방법.

임계 영역에 들어가려는 프로세스는 진입 코드를 수행해야 한다.

  • 임계 영역 수행 전, 키를 얻어서 임계 영역의 잠금을 풀고 진입한다.
  • 프로세스가 키를 반환할 때까지 다른 모든 프로세스들은 잠금 상태를 유지한다.

임계 영역을 떠나는 프로세스들은 탈출 코드를 수행함으로써 다른 프로세스가 임계 영역에 들어갈 수 있도록 한다.

 

3.8) 병행 프로세스의 코드 영역 구분

  • 진입 영역: 임계 영역에 들어갈 수 있는지 알아보고 허락을 받는 부분.
  • 임계 영역: 한 순간에 한 프로세스만 실행해야 하는 코드 부분.
  • 탈출 영역: 임계 영역 수행을 마치고 나갈 때 필요한 작업을 수행하는 부분.
  • 나머지 영역: 임계 영역과 관련 없는 나머지 코드 부분.

 


 

4. 상호 배제 방법들

4.1) 상호 배제와 동기화 문제 해결책들

임계 영역 접근을 관리하는 부분에서 발생할 수 있는 문제들을 해결할 수 있는 방안들은 다음과 같다.

  • 소프트웨어적인 임계 영역 문제 해결
  • 하드웨어 명령어를 이용한 임계 영역 문제 해결
  • 세마포어와 모니터

 

4.2) 소프트웨어적인 임계 영역 문제 해결

은 특별한 하드웨어 명령어가 불필요한 해결책이다.

바쁜 대기가 발생하기 때문에 프로세서 시간이 낭비된다.

바쁜 대기란, 어떤 조건을 기다리는 프로세스들이 비생산적이고 자원을 소비하는 대기 루프에 남아 있는 것을 말한다.

2개의 프로세스, P0과 P1이 있다고 가정할 때, 사용할 수 있는 해결책은 다음과 같다.

  • 데커(Dekker)의 알고리즘
  • 페터스(Peterson)의 알고리즘

 

4.3) 데커의 알고리즘

데커의 알고리즘은 2개의 프로세스의 임계 영역 문제를 HW 도움 없이 순수히 SW적으로 해결하는 알고리즘이다.

임계 영역 문제 해결책의 조건들(상호 배제, 진행, 한정 대기)을 만족한다.

프로세스가 P0, P1일 때, flag[0], flag[1]이라는 2개의 공유 변수를 사용한다.

2개의 공유 변수는 bool 형 변수로서, true 또는 false 값 중 하나를 가진다.

turn 변수는 진입 우선권을 가지는 프로세스를 지정하는 정수형 변수이다.

0 또는 1의 값 중 하나를 가지며, turn이 0이면 P0이, turn이 1이면 P1이 진입 우선권을 가진다.

프로세스 P0는 다음 코드를 반복 수행한다.

/* 프로세스가 공유하는 데이터 */
//flag[]: bool 배열, turn: 정수
flag[0] = false;
flag[1] = false;
turn = 0;	// 공유 변수; 0 또는 1


/* 진입 영역 */
flag [0] = true;             // P0의 임계 영역 진입 표시

while (flag[1] == true) {    // P1의 임계 영역 진입 여부 확인
    if (turn == 1) {         // P1이 진입 차례라면,
        flag[0] = false;     // 임계 영역 진입 차례를 P1에게 양보
        while (turn == 1) {  // turn 공유 변수 값이 0이 될 때까지 바쁜 대기
            // 바쁜 대기
        }
        flag[0] = true;      // P0이 임게 영역 진입을 다시 시도
    }
}
/////


/* 임계 영역 */
/////


/* 탈출 영역 */
turn = 1;	        // P1에게 진입 순서 제공
flag[0] = false;	// P0의 임계 영역 사용 완료 지정
/////


/* 나머지 영역 */
/////

 

4.4) 페터슨의 알고리즘

페터슨의 알고리즘은 데커의 알고리즘과 동일하게 SW로만 해결하는 방안이지만, 더 단순한 알고리즘이다.

/* 공유 변수 초기화 */
flag[0] = false;
flag[1] = false;
turn = 0;


// 프로세스 P0 //
while (true) {
    /* 진입 영역 */
    flag[0] = true;                  // P0 임계 영역 진입 표시
    turn = 1;                        // 진입 우선권 표시
    while (flag[1] && turn == 1) {   // 만약 P1이 임계 영역에 있다면,
        // 바쁜 대기
    }
    /////
    
    
    /* 임계 영역 */
    /////
    
    
    flag[0] = false;     // 임계 영역 탈출을 알림
    
    
    /* 나머지 영역 */
    /////
}

 

위의 두 알고리즘에서 공유 변수로 사용된 turn 변수는 이전에 봤던 counter 변수처럼 기계어 명령어 처리 단계가 여러 단계로 나눠져 처리되지 않는다.

변수에 저장되는 값을 0에서 1로, 1에서 0으로 바꾸는 것뿐이기 때문에, 명령어 1줄로 처리된다.

그러므로 해당 변수에 대한 동시 접근 문제를 고려하지 않아도 된다.

 

4.5) 하드웨어 명령어 이용

특수한 하드웨어 명령어를 제공하는 경우, 순수한 SW 처리 방법보다 임계 영역 문제를 해결하는 알고리즘이 단순해진다.

임계 영역 문제 해결에 사용할 수 있는 하드웨어 명령어는 다음과 같다.

  • TestAndSet: 내용을 검사 및 수정하는 하드웨어 명령어
  • Swap: 두 내용을 서로 바꾸는 하드웨어 명령어

하드웨어 명령어 이용 방법은 명령어를 제공하지 않는 시스템에서는 사용할 수 없다.

 

4.6) TestAndSet 명령어

TestAndSet 명령어는 다음과 같은 원자적 연산을 수행한다.

원자적 연산이란, 중간에 방해를 받지 않고 어셈블리어 명령어 하나로 실행하는 연산을 말한다.

// target을 검사하고, target 값을 true로 설정한다.
// 임계 영역 진입 가능 여부 검사, 키 값 변경, 진입을 동시에 처리한다.
boolean TestAndSet(boolearn *target) {
    boolean temp = *target;    // 이전 값 기록
    *target = true;            // true로 값 변경
    
    return temp;               // 값 변환
}

TestAndSet을 이용한 임계 영역 문제 해결 방안은 단일 프로세서만이 아니라 메모리를 공유하는 다중 처리 환경에서도 적용할 수 있다.

임계 영역에 진입하려는 프로세스에서 바쁜 대기가 발생한다.

프로세스가 2개 초과이면 일부가 무기한 연기에 빠질 수도 있다.

TestAndSet을 이용한 임계 영역 문제 해결 방안은 다음과 같다.

// 공유 변수 초기화
lock = false;
waiting[i] = false;    // (i = 0, 1, 2, ~ n-1)


while (true) {
    /* 진입 영역 */
    waiting[i] = true;  // 임계 영역에 진입하려는 프로세스 항목을 true로 변경
    key = true;         // 키 값 할당
    
    while (waiting[i] && key) {  // 임계 영역에 진입하려는 프로세스가 키 값을 가지고 있다면,
        key = TestAndSet(lock); // TestAndSet() 실행; 키 값 검사, 진입, 키 값 변경 동시 처리
    }
    
    waiting[i] = false;  // 임계 영역에 진입한 프로세스는 대기 상태가 아님
    /////
    
    
    /* 임계 영역 */
    /////
    
    
    /* 탈출 영역 */
    j = (i + 1) % n;  // 대기 중인 프로세스를 탐색하기 위한 인덱스 계산 수식
    
    while ((j != i) && !waiting[j]) {  // 대기 중인 프로세스 탐색
        j = (j + 1) % n;
    }
    
    if (j == i) {            // 대기 중인 프로세스가 없다면,
        lock = false;        // 다른 프로세스의 진입 허용
    }
    else {
        waiting[j] = false; // 대기 중이었던 프로세스 Pj가 진입할 수 있도록 함
    }
    
    
    /* 나머지 영역 */
    /////
}

 

4.7) 세마포어(Semaphore)

앞에서 다룬 임계 영역 문제 해결 방법들은 좀 더 복잡한 동기화 문제에서 다루기가 어렵다.

세마포어란, 초기화 이후에는 2가지 연산(P연산, V연산)으로만 접근할 수 있는 보호된 정수 변수이다.

세마포어의 예시로는 '열차 차단기'가 있다.

  • 차단기가 올라가면 정지/대기 상태로, 자원이 없어 기다려야 하는 경우를 나타낸다.
  • 차단기가 내려가면 진행 상태로, 프로세스가 해당 자원을 사용할 수 있는 경우를 나타낸다.

세마포어의 종류는 다음과 같다.

  • 이진 세마포어: 0 또는 1 중에 하나의 값을 가진다. Ex) 상호 배제 문제 해결을 위해 1로 초기화.
  • 계수 세마포어: 정수 값을 가진다. Ex) 유한한 자원에 접근하는 것을 제어하기 위해 사용 가능한 자원 수로 초기화.

세마포어 연산은 다음과 같은 단계로 나눠진다.

  • 초기화
  • P 연산: 네덜란드어로 검사를 나타낸다. 프로세스를 대기시키는 Wait 동작이다. 임계 영역 문제에서는 진입 영역(Entry Section)으로 사용된다.
  • V 연산: 네덜란드어로 증가를 나타낸다. 대기 중이던 프로세스를 깨우는 신호를 보내는 Signal 동작이다. 임계 영역 문제에서는 탈출 영역(Exit Section)으로 사용된다.

세마포어 S에 대한 P, V 연산은 다음과 같이 wait(S), signal(S)로 정의할 수 있다.

// 세마포어 S에 대한 P 연산
wait (S) {		
    while (S <= 0);    // 세마포어 S의 값이 0 이하일 경우 대기; 자원 할당 불가
    S--;               // 대기가 끝나면 임계 영역에 진입하므로, 세마포어 S 값을 -1
}


// 세마포어 S에 대한 V 연산
signal (S) {
    S++;             // 임계 영역에서 탈출하였으므로, 세마포어 S 값을 +1
}

프로세스 1개가 세마포어 값을 수정하고자 할 때, 다른 프로세스가 세마포어 값을 동시에 수정할 수 없어야 한다.

즉, P 연산에서 S값 검사와 수정 V 연산인터럽트 없이 단일 동작으로 수행해야 한다.

P 연산, V 연산 동작은 프로세스가 시스템을 호출함으로써 운영체제에 의해 실행된다.

P 연산을 요구한 프로세스는 수행 가능한 상태(S > 0)가 될 때까지 기다려야 한다.

세마포어는 2개 이상의 프로세스에 대한 임계 영역 문제를 해결하는 데 사용할 수 있다.

n개의 프로세스가 1로 초기화된 세마포어를 공유한다.

각 프로세스 Pi의 구조는 다음과 같다. (i = 0, 1, 2, ~, n-1)

// Pi는 세마포어 mutex 값을 공유한다.
int mutex = 1;

while (true) {
    wait(mutex);    // P 연산
    
    /* 임계 영역 */
    /////
    
    signal(mutex);  // V 연산
    
    /* 나머지 영역 */
    /////
}

 

세마포어는 여러 가지 동기화 문제를 다루는 데에도 사용할 수 있다.

Ex) 2개의 프로세스: 문장 S0을 가진 P0, 문장 S1을 가진 P1

  • 조건: S0이 끝난 후에 S1을 수행해야 한다.
  • 구현 방법: P0과 P1이 세마포어 synch를 공유(0으로 초기화)한다. P0에는 signal을, P1에는 wait을 삽입한다.
/* 프로세스 P0 */
S0;
signal(synch);    // V 연산; S0 문장 실행이 끝났음을 알림


/* 프로세스 P1 */
wait(synch);     // P 연산; S0 문장 실행이 끝날 때까지 대기
S1;

바쁜 대기 문제를 해결하도록 세마포어 연산을 구현하는 방법은 다음과 같다.

  • 프로세스가 P 연산 실행 시, 세마포어 값이 음수인 경우 프로세스는 세마포어의 대기 리스트에서 대기한다.
  • 프로세서 스케줄러가 준비 큐에서 다른 프로세스를 선택한다.
  • 세마포어에서 대기 중인 프로세스는 다른 프로세스가 V 연산을 실행해야 대기 상태에서 탈출할 수 있다.
  • 대기 상태에서 탈출한 프로세스는 준비 큐에 들어간다.
/* 세마포어 구조 정의 */
struct Semaphore(S) {
    int count;       // 정수형 변수
    queueType queue; // 대기 리스트; 큐
}

Semaphore(S);
/* 세마포어 S에 대한 P 연산 */
// S가 자원 할당을 위한 세마포어라면, 사용 가능한 자원 수로 count를 초기화한다.
wait(S) {
    S -> count--;   
    if (S -> count < 0) {   // 사용 가능한 자원이 없을 경우,
        // 이 연산을 실행한 프로세스를 S -> queue에 삽입
        // 대기(보류) 상태로 전이; (block() 실행)
    }
}


/* 세마포어 S에 대한 V연산 */
signal(S) {
    S -> count++;
    if (S -> count <= 0) {
        // S -> queue의 프로세스 1개를 제거
        // 준비 상태로 전이; (wakeup() 실행)
    }
}

 

세마포어를 구현할 때는 프로세스들이 동시에 P연산, V연산을 수행할 수 없도록 해야 한다.

단일 프로세서인 경우, P연산, V연산을 수행하는 중에 인터럽트를 금지하면 된다.

인터럽트를 금지하면, 다른 프로세스의 명령어 실행이 중간에 끼어들지 않는다.

다중 프로세서인 경우, 인터럽트 금지만으로는 해당 문제를 해결할 수 없다.

하드웨어가 특별한 명령을 제공하지 않으면, 소프트웨어적으로 해결해야 한다.

만약 SW적으로 동시 연산을 막고자 한다면, P 프로시저와 V 프로시저가 임계 영역이 된다.

세마포어를 사용할 때에는 다음과 같은 유의사항을 지켜야 한다.

  • P 또는 V 연산을 빠뜨리거나 잘못 사용하는 경우: 상호 배제, 동기화 작업이 제대로 이루어지지 않을 수 있다. P 연산 과정에서 대기하고 있던 프로세스들이 교착 상태에 빠질 수 있다.

 

4.8) 모니터

세마포어의 P, V 연산은 프로그램 전체에 퍼져 있고, 이들이 연산에 미치는 영향을 파악하기가 어렵다. (프로그램 작성 난이도 상승)

세마포어의 이러한 문제를 해결하기 위해 핸슨(Brich Hansen)과 호어(Hoare)가 모니터를 개발하였다.

모니터는 동기화를 자동으로 제공하는 병행 프로그래밍 구조이다.

1개의 모니터는 다음과 같이 구성된다.

  • 공유 데이터: 프로시저들이 고유하는 변수
  • 모니터 진입 루틴: 모니터 서비스를 사용하기 위해 외부에서 호출할 수 있는 프로시저
  • 초기화 코드

 

4.9) 모니터의 구조

모니터의 구조는 다음과 같다.

모니터는 공유 데이터, 1개 이상의 프로시저, 초기화 코드로 구성된 객체이다.

모니터 밖에 있는 프로세스모니터 데이터에 접근이 불가능하다.

모니터 경계에서 1번에 1개의 프로세스만 진입하도록 제어하므로, 상호 배제 원칙이 지켜진다.

  • 1개의 프로세스가 모니터 진입 루틴을 호출하여 실행 중이면, 진입을 원하는 다른 프로세스는 모니터 밖 진입 큐에서 대기해야 한다.
  • 프로그래머는 동기화 제약 조건을 명시적으로 코드화할 필요가 없다.

 

4.10) 모니터의 문법 구조

모니터의 문법 구조는 다음과 같다.

Monitor Monitor_Name {
    Type1 variable_name1;	// 공유 데이터 변수 선언
    Type2 variable_name2;
    
    procedure P1() {	// 모니터 진입 루틴
    
    }
    
    procedure P2() {
    
    }
    
    procedure P3() {
    
    }
    
    initialization_code() {	// 초기화 코드
    
    }

모니터는 다음과 같은 방법으로 활용될 수 있다.

  • 임계 영역 문제 해결: 모니터 프로시저는 임계 영역에 해당한다.
  • 모니터를 이용한 자원 할당: 자원 할당을 담당하는 1개의 모니터 객체를 생성한다. 자원을 요청하는 프로세스는 모니터 진입 루틴을 호출한다. 자원을 할당받으면 프로시저 수행을 마치고 모니터를 떠난다. 자원을 할당받지 못하면 큐에서 대기한다. 자원을 반납하는 프로세스는 모니터 진입 루틴을 호출한다. 호출된 루틴은 대기 중인 프로세스에 신호를 보내고, 모니터는 대기 중인 프로세스에 자원을 할당한다.

모니터의 루틴을 수행하는 중에 프로세스가 대기하는 경우, 대기 조건을 나타내기 위해 조건 변수를 둘 수 있다.

모니터의 조건 변수는 1개 이상 정의할 수 있으며, wait과 signal 연산만 수행할 수 있다.

// 2개의 조건 변수, x, y를 정의하는 경우
condition x, y;
// 조건 변수 x에 대해,
x.signal;  // signal 연산 수행
x.wait;    // wait 연산 수행

또한 조건 변수마다 를 지니고 있다.

x.wait을 실행한 프로세스는 x의 큐에 삽입되고, y.wait을 실행한 프로세스는 y의 큐에 삽입된다.

모니터의 조건 변수가 처리하는 wait과 signal 연산은 다음과 같은 역할을 수행한다.

  • wait 연산: wait 연산을 실행한 프로세스는 다른 프로세스가 signal 연산을 실행할 때까지 큐에서 대기한다.
  • signal 연산: 이전에 wait 연산을 실행하여 큐에서 대기 중인 프로세스들 중 1개를 꺼내 모니터로 진입할 수 있도록 한다. 큐에서 대기 중인 프로세스가 없다면, signal 연산을 실행해도 변화가 없다.

 

4.11)조건 변수를 가지는 모니터의 구조

조건 변수를 가지는 모니터의 구조는 다음과 같다.

 


 


수고하셨습니다!


 

'Develop Study > Operating System' 카테고리의 다른 글

6. 프로세스 스케줄링  (0) 2021.11.04
5. 교착 상태와 기아 상태  (0) 2021.10.16
3. 프로세스와 스레드  (0) 2021.10.12
2. 운영체제  (0) 2021.10.12
1. 컴퓨터 시스템  (0) 2021.10.04
Comments