본문 바로가기
3학년 학사/운영체제

[운영체제] #9. 동기화 (1)

by whiteTommy 2024. 11. 27.
반응형
더보기

목차

  • Race Condition
  • Critical Section
  • Synchronization Instruction
  • Semaphores
  • Deadlock
  • Monitor

 

Race Condition

  • 정의
    • 여러 프로세스가 동시에 공유 데이터에 접근하여 일관성(Consistency)이 깨질 수 있는 상황
    • 공유 데이터:
      • 여러 프로세스가 동시에 접근하여 데이터를 읽거나 쓰는 자원.
      • Race Condition이 발생하면 데이터가 예기치 않게 변경될 수 있음.
  • 문제 해결 필요성
    • 데이터 일관성을 유지하려면 프로세스 간 동기화가 필요
    • 특히 협력(Cooperating) 하는 프로세스들이 공유 자원을 안전하게 사용하도록 순차적 실행이 보장되어야 함
  • 예제
    • 1000원의 balance 가 남아있을 때, 500원의 입금과 500원의 출금이 동시에 일어날 경우, 그 결과는?
      • 두 연산이 동시에 처리될 경우, 최종 잔액이 예상치와 다르게 계산될 수 있음
    • 컴파일 하기 전의 소스코드

  • 컴파일러의 최적화
    • 컴파일러는 Balance 값을 메모리에서 읽고 변경 후 다시 메모리에 기록하는 식으로 최적화.
    • 코드가 분할되어 순차적으로 처리되지 않을 가능성이 있음:
      • 프로세스 A와 B가 동시에 실행되면 중간값이 잘못된 결과를 초래.
  • 결과
    • 최종적으로 잔액이 500원 또는 1500원이 되는 불일치가 발생.

 

Interleaved Execution

  • 프로세스 A와 B의 실행 흐름
    • 프로세스 A: 현재 계좌 잔액(Balance)을 읽고 500을 더함.
    • 프로세스 B: 같은 잔액을 읽고 500을 뺌.
  • 결과
    • 두 프로세스가 동시에 실행되면 순서에 따라 결과가 다를 수 있음.
    • 최종 값이 잘못 계산될 가능성이 큼 → 데이터의 일관성 손실 발생.

 

Critical Section

  • 정의
    • 여러 프로세스가 공유 자원에 접근할 때 동시에 실행되면 문제가 생기는 코드 영역.
      ex) 은행 계좌의 잔액(Balance)을 읽고 쓰는 코드.
  • 구조
    • Entry Section: 프로세스가 Critical Section에 진입하기 전에 동기화를 통해 실행 순서를 조정.
    • Critical Section: 공유 자원에 접근하여 작업을 수행하는 코드 영역.
    • Exit Section: 작업이 완료된 후 Critical Section을 나가는 코드.
    • Remainder Section: Critical Section 외의 나머지 작업.

 

Critical Section 해결의 조건들

Critical Section 문제를 해결하려면 다음 세 가지 조건을 만족하는 알고리즘을 사용해야 한다.

  1. Mutual Exclution (상호 배제)
    • 한번에 하나의 프로세스만 Critical Section 에 진입 가능
    • 다른 프로세스는 현재 프로세스가 Critical Section 에서 나올 때까지 대기해야 함
  2. Progress (진행)
    • Critical Section 에 프로세스가 없으면, 진입을 대기 중인 프로세스 중 하나가 진입 가능해야 함
    • 무한 대기 방지
  3. Bounded Waiting (한정된 대기)
    • 프로세스가 Critical Section 에 진입하기까지의 대기 시간이 제한되어야 함
    • 특정 프로세스가 무한정 대기하지 않도록 보장

 

두 Process 를 위한 Algorithm

  • Shared Variables 
    • int turn : 두 프로세스가 번갈아가며 Critical Section 에 진입하도록 조정하는 변수 
    • turn = 0 일 때,
      • Mutual Exclusion (O)
      • Progress, Bounded Waiting (X)

 

다른 Algorithm

  • Shared Variables
    • boolean flag[2]; flag[0] = flag[1] = false 로 초기화
    • flag[0] = true 일 때, $P_{0}$ 이 critical section에 진입 가능
    • flag[1] = true 일 때, $P_{1}$ 이 critical section에 진입 가능
      • Mutual Exclusion (O)
      • Progress, Bounded Waiting (X)
        1. $P_{0}$ 실행, flag[0] = true, flag[1] = false
        2. context switching $P_{1}$ 실행, flag[0] = true, flag[1] = true 
        3. $P_{1}$ 무한대기
        4. $P_{0}$ 무한대기

 

Peterson Solution

  • Shared Variables
    • int turn;
    • boolean flag[2]; flag[0] = flag[1] = false 로 초기화
      • Mutual Exclusion, Progress, Bounded Waiting (O)
        • $P_{0}$ 실행, flag[0] = true, flag[1] = false
        • context switching, $P_{1}$ 실행, flag[0] = true, flag[1] = true
        • turn = 0; $P_{1}$ 무한 대기
        • $P_{0}$ 실행, turn = 1, $P_{0}$ 무한 대기
        • context switching $P_{1}$ critical section 진입 ...

 

Peterson Solution의 한계

  • 세 개 이상의 프로세스가 참여할 경우 확장이 어려움
  • 확장된 Algorithm 의 증명도 NP 문제
  • SW 로 해결하는 대신 하드웨어로 처리하면 알고리즘이 매우 간단하게 됨

 

간단한 방법

  • Critical Section 에 들어가면서 Interrupt 를 Disable 한다
    • Interrupt 를 비활성화하면, 다른 프로세스가 CPU 를 차지할 수 없으므로 Critical Section 에 안전하게 접근 가능
  • User Program 이 Interrupt 를 Control 하는 것은 바람직하지 않음
  • Scalable 하지 않음
    • process 의 숫자가 많아 질 때 성능 저하 및 문제가 생길 수 있음
  • 동기화를 위한 Instruction 이 도입됨

 

Synchronization Instruction

  • CPU 에서 지원하여 원자적 (Atomically) 으로 수행되는 명령어(Instruction) 이용
    • 명령어가 수행되는 동안 방해 받지 않음(= Uninterruptible)

1. Test-and-Set

  • 메모리에서 값을 읽고(Test), 동시에 값을 변경(Set)
  • 실행 도중 다른 프로세스의 개입을 허용하지 않음(interrupt 불가)
boolean TestAndSet(boolean *target) {
    boolean rv = *target;
    *target = true;
    return rv;
}
  • 변수
    • rv : 이전 값 (현재 잠금 상태) 을 반환
    • *target = true : 잠금을 설정
  • 동작
    • lock = false
    • lock = true
    • rv = false, TestAndSet(&lock) = rv = false 이므로 critical section 에 진입
    • lock = false 가 실행되기 전까지는 lock = true 이므로 잠긴다.
    • lock = false 가 실행되면, 다른 Process 가 진행이 가능하다.

 

2. Swap

  • Swap 명령어는 두 변수의 값을 교환하는 원자적(Atomic) 연산
  • 이 명령어는 하드웨어적으로 동작하며, 두 변수의 값을 서로 바꾸는 동작을 중단 없이 수행
  • Shared Variables
    • boolean lock
    • boolean waiting[n]
void Swap(boolean *a, boolean *b) {
    boolean temp = *a;
    *a = *b;
    *b = temp;
}

 

  • 입력: 두 변수(*a, *b).
  • 동작:
    • 임시 변수(temp)에 *a 값을 저장.
    • *a에 *b 값을 복사.
    • *b에 temp 값을 복사하여 두 값 교환.
  • 특징:
    • Swap 연산은 원자적으로 실행되며, 도중에 중단되지 않음.
Process Pi:
do {
    waiting[i] = true;                  // 자신이 대기 상태임을 표시
    while (waiting[i] == true)          // 다른 프로세스가 끝날 때까지 대기
        swap(&lock, &waiting[i]);       // Swap 연산으로 Critical Section 접근 시도
    // Critical Section
    lock = false;                       // Critical Section 종료 후 잠금 해제
    // Remainder Section
} while (true);

 

  • $P_{1}$ 실행, waiting[1] = true
  • while 문 진입, swap 호출
  • lock = false, waiting[1] = true 교환
    • lock = true, waiting[1] = false
  • while 문 종료
  • $P_{1}$ critical section 진입
  • context swtiching $P_{2}$ 실행, waiting[2] = true, lock = true
  • while 문 진입, swap 호출
  • lock = true, waiting[2] = true, while 문 무한루프
  • $P_{1}$ context switching 되고 lock= false 실행하고, 이후엔 $P_{2}$ 이 critical section 진입 가능

 

위와 같은 Instruction 의 한계점

  • 동기화 Instruction 을 쓰면, Mutual Exclusion 은 해결, Bounded Waiting 같은 조건은 User Program 에서 제공해야함
  • Bounded Waiting 이 주어진 문제마다 조금씩 차이가 있기 때문, User Program 에서 제대로 처리하는 것을 기대하기 어려움
  • 좀 더 Comprehensive 한 동기화 Primitive 가 필요함

 

 

Semaphore

  • 정의
    • 동기화 문제를 해결하기 위한 정수 변수
    • 두 개의 원자적 연산을 통해 Critical Section 에 대한 접근을 제어
      1. P() 또는 Wait() : 자원을 사용하기 위해 대기
        • Critical Section 들어가기 전에 수행
      2. V() 또는 Signal() : 자원을 반환하여 다른 프로세스가 사용 가능하도록 허용
        • Critical Section 나와서 수행

 

  • 특징
    • 원자적으로 수행됨
      • 한 번에 하나의 프로세스만 연산을 수행 가능
      • 하나의 Process 가 P 를 수행하여 세마포어의 값을 수정하는 동안, 다른 Process 에서 P나 V 를 수행하여 같은 세마포어의 값을 수정하지 못함
    • 세마포어 값(S) 는 공유 자원의 상태를 나타냄
      • S > 0 : 사용 가능한 자원이 존재
      • S <= 0 : 자원이 사용 중이며, 대기 중인 프로세스가 있음

 

Semaphores (세마포어) 의 구현

1. Busy Waiting

P(S)
while (S <= 0);  // 조건을 만족할 때까지 반복
S = S - 1;       // 자원 감소

V(S)
S = S + 1;
  • 동작
    • S = 1 : Critical Section 이 비어있음
    • $P_{1}$ 실행, P() : S = S - 1
    • S= 0, $P_{1}$ 이 Critical Section 진입
    • Context Switching $P_{2}$  실행, S=0, 무한대기 (Busy Waiting)
    • Context Switching $P_{1}$ 실행, V() : S = S+1, S= 1
    • Context Switching $P_{2}$ 실행, 자원이 남아있으므로, Critical Section 진입 가
  • 단점
    • CPU 소모:
      • Busy Waiting 방식에서는 while 루프를 통해 CPU가 지속적으로 조건을 검사.
      • Critical Section에 진입 가능한 상태가 될 때까지 반복하므로 CPU 자원이 낭비됨.
    • 누가 진입할지 결정하지 않음:
      • 대기 Process 중 누가 Critical Section 에 진입할지 결정하지 않음

2. Sleep Queue

  • Busy Waiting 의 문제 해결
    • Sleep Queue 를 사용하여 대기 중인 프로세스를 CPU 에서 제외하고 Sleep 상태로 전환
    • Critical Section 이 비게 되면 (S > 0) Sleep Queue 에서 깨워 실행 (자료구조 필요)
  • 자료구조
typedef struct {
    int value;               // 세마포어 값
    struct process *list;    // Sleep Queue (대기 중인 프로세스 목록)
} semaphore;

P(semaphore *S)
if (S->value <= 0) {
    add this process to S->list;  // 대기 중인 프로세스를 Sleep Queue에 추가
    block();                     // 프로세스를 Sleep 상태로 전환
}
S->value--;                      // 자원 감소

V(semaphore *S)
S->value++;                      // 자원 증가
if (S->value > 0) {
    remove a process from S->list;  // Sleep Queue에서 대기 프로세스 제거
    wakeup(p);                      // 제거된 프로세스를 실행 상태로 전환
}
  • 변수
    • value:
      • 자원 사용 가능 여부를 나타냄.
      • value > 0: 사용 가능한 자원이 존재.
      • value <= 0: 자원이 모두 사용 중이며 대기 프로세스가 존재.
    • list:
      • 대기 중인 프로세스를 관리하는 Sleep Queue.
  • 장점
    • CPU 효율성: 대기 프로세스는 Sleep 상태로 전환되어 CPU 자원을 사용하지 않음.
    • 공정성 보장: Sleep Queue에 추가된 프로세스는 FIFO 방식으로 관리되어 공정성이 보장됨.
    • 확장성: 다중 프로세스 환경에서도 효율적으로 동작.

 

Semaphores (세마포어) 의 종류

  1. Counting Semaphore
    • 정의:
      • 자원의 개수가 여러 개일 때 사용. (범위가 정해져 있지 않음)
      • 초기 값은 가능한 자원의 수로 정해짐
    • 특징: 자원의 수가 동적으로 변화 가능.
      ex) 데이터베이스 연결, 프린터 큐 등.
  2. Binary Semaphore
      • 정의:
        • S = 1: Critical Section이 비어 있음.
        • S = 0: Critical Section이 사용 중.
      • 특징:
        • Counting Semaphore 보다 구현이 간단함
        • 이를 이용하여 Counting Semaphore 를 구현할 수 있음
        • Test-and-Set 과 같은 Hardware 의 도움을 받아서 구현할 수 있음

 

Binary Semaphores (세마포어) 의 구현

  • Test-and-Set 명령어를 이용하여 구현
  • Semaphore S : 현재 Critical Section 에 진입한 Process 가 있는지 나타냄 (True or False)
    • P(S) : while(test_and_set(&S))
      • S 값이 return 되고, S 는 True 로 바뀜
    • V(S) : S = false
      • 진입한 Process 가 없음을 나타내어 wait () 에서 대기중인 Process 를 진입 가능하도록 함
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>

// Test-and-Set 함수
boolean TestAndSet(boolean *target) {
    boolean old_value = *target; // 이전 값을 저장
    *target = true;              // target 값을 true로 설정
    return old_value;            // 이전 값을 반환
}

// Binary Semaphore 구조
typedef struct {
    boolean lock; // Critical Section 상태 (true = 사용 중, false = 비어 있음)
} BinarySemaphore;

// P(S) 연산 (Wait)
void P(BinarySemaphore *S) {
    while (TestAndSet(&S->lock)) {
        // Busy Waiting: lock이 해제될 때까지 반복
    }
}

// V(S) 연산 (Signal)
void V(BinarySemaphore *S) {
    S->lock = false; // Critical Section 해제
}

// Process 예제
void Process1(BinarySemaphore *S) {
    P(S); // Critical Section 진입 시도
    printf("Process 1: Critical Section Start\n");
    sleep(1); // Critical Section 작업
    printf("Process 1: Critical Section End\n");
    V(S); // Critical Section 해제
}

void Process2(BinarySemaphore *S) {
    P(S); // Critical Section 진입 시도
    printf("Process 2: Critical Section Start\n");
    sleep(1); // Critical Section 작업
    printf("Process 2: Critical Section End\n");
    V(S); // Critical Section 해제
}

int main() {
    BinarySemaphore S = {false}; // 초기 상태: Critical Section이 비어 있음

    if (fork() == 0) {
        Process1(&S); // 자식 프로세스 실행
    } else {
        Process2(&S); // 부모 프로세스 실행
    }

    return 0;
}
  • 동작
    • 초기 상태
      • lock = false : Critical Section 이 비어 있음
    • $P_{1}$ 실행
      • P(S) 호출
        • Test_and_Set (&S->lock) 에서 lock = false
        • lock = true 로 설정되고 false 리턴, critical section 진입
    • Context Switching $P_{2}$ 실행
      • P(S) 호출, lock = true
      • Test_and_set (&S->loc) 에서 lock = true
      • lock = true 로 설정되고 true 리턴, busy waiting
    • Context Switching $P_{1}$ 실행
      • V(S) 호출
        • lock = false 로 설정하여 Critical Section 해제
    • Context Switching $P_{2}$ 실행
      • Busy waiting 종료, lock = false
      • lock = true 설정되고 false 리턴
      • Critical Section 진입

Counting Semaphores (세마포어) 의 구현

  • Counting Semaphore = CS, CS 의 Value = C
  • 2개의 Binary Semaphore S1, S2 사용
  • S1 = 1, S2 = 0 로 초기화 (C가 양수로 가정)
    • S1 : CS 의 자원 수를 제어
    • S2 : 대기 Process 관리

 

  • Wait Operation (P(S))
    • 자원(C)이 남아 있다면 즉시 Critical Section에 진입.
    • 자원이 부족하다면: 대기 상태로 전환.
  • Signal Operation (V(S))
    • 자원을 반환하며 대기 상태에 있는 프로세스를 깨움.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

// Binary Semaphore를 구조체로 정의
typedef struct {
    sem_t sem;  // 세마포어
} BinarySemaphore;

// Binary Semaphore 초기화
void initBinarySemaphore(BinarySemaphore *bsem, int value) {
    sem_init(&bsem->sem, 0, value);
}

// P(S) 연산 (Wait)
void P(BinarySemaphore *bsem) {
    sem_wait(&bsem->sem); // 세마포어 값을 감소, 0이면 대기
}

// V(S) 연산 (Signal)
void V(BinarySemaphore *bsem) {
    sem_post(&bsem->sem); // 세마포어 값을 증가, 대기 중인 프로세스를 깨움
}

// Counting Semaphore 구조체 정의
typedef struct {
    int C;                  // 현재 자원의 개수
    BinarySemaphore S1;     // Binary Semaphore 1
    BinarySemaphore S2;     // Binary Semaphore 2
} CountingSemaphore;

// Counting Semaphore 초기화
void initCountingSemaphore(CountingSemaphore *csem, int initialValue) {
    csem->C = initialValue;           // 초기 자원 개수 설정
    initBinarySemaphore(&csem->S1, 1); // S1은 1로 초기화
    initBinarySemaphore(&csem->S2, 0); // S2는 0으로 초기화
}

// Wait Operation (P(S))
void P_CountingSemaphore(CountingSemaphore *csem) {
    P(&csem->S1);       // S1을 P(S) 호출
    csem->C--;          // 자원 개수 감소
    if (csem->C < 0) {  // 자원이 부족하면
        V(&csem->S1);   // S1을 해제
        P(&csem->S2);   // S2에서 대기 (Sleep Queue 역할)
    } else {
        V(&csem->S1);   // S1을 해제
    }
}

// Signal Operation (V(S))
void V_CountingSemaphore(CountingSemaphore *csem) {
    P(&csem->S1);       // S1을 P(S) 호출
    csem->C++;          // 자원 개수 증가
    if (csem->C <= 0) { // 대기 중인 프로세스가 있으면
        V(&csem->S2);   // S2에서 깨움
    }
    V(&csem->S1);       // S1을 해제
}

// 테스트용 쓰레드 함수
void *process(void *arg) {
    CountingSemaphore *csem = (CountingSemaphore *)arg;

    printf("Process %ld: Waiting to enter Critical Section...\n", pthread_self());
    P_CountingSemaphore(csem); // Critical Section 진입 대기
    printf("Process %ld: Entering Critical Section\n", pthread_self());
    sleep(1); // Critical Section에서 작업 수행
    printf("Process %ld: Leaving Critical Section\n", pthread_self());
    V_CountingSemaphore(csem); // Critical Section 해제
    return NULL;
}

int main() {
    CountingSemaphore csem;
    initCountingSemaphore(&csem, 2); // 초기 자원 수 = 2 (동시에 2개 프로세스만 진입 가능)

    pthread_t threads[5];
    for (int i = 0; i < 5; i++) {
        pthread_create(&threads[i], NULL, process, &csem);
    }

    for (int i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}

 

Process 12345: Waiting to enter Critical Section...
Process 12345: Entering Critical Section
Process 12346: Waiting to enter Critical Section...
Process 12346: Entering Critical Section
Process 12347: Waiting to enter Critical Section...
Process 12345: Leaving Critical Section
Process 12347: Entering Critical Section
Process 12346: Leaving Critical Section
Process 12347: Leaving Critical Section
  • 초기 상태
    • C = 1
    • Binary Semaphore S1 : 1 (Counting Semaphore 접근 가능)
    • Binary Semaphore S2 : 0 (Sleep Queue, 대기 중인 프로세스 없음)
  • 동작
    • $P_{1}$ 실행, P(S1) 호출
    • S1 = 0, C = 0
    • V(S1) 호출, S1 = 1
    • P(S2) 호출
    • $P_{1}$ Critical Section 진입
    • Context Switching $P_{2}$ 실행, P(S1) 호출
    • S1 = 0,  C = -1
    • V(S1) 호출, S1 = 1
    • P(S2) 호출 : Sleep Queue 에서 대기
    • $P_{1}$ 가 Critical Section 을 완료하고 V(S) 호출
    • P(S1) 호출, S1 = 0, C=0
    • V(S2) 호출, Sleep Queue 에서 대기 중인 $P_{2}$ 깨움
    • V(S1) 호출, S1 = 1

 

Semaphore 의 구현 

  • Kernel 이 Single Thread 인 경우
    • P()와 V() 연산이 System Call로 호출.
    • Non-Preemptive Kernel에서 동작:
      • Kernel 내에서 세마포어 동작이 보장됨.
      • Kernel이 Critical Section을 실행 중일 때는 다른 프로세스가 Kernel에 접근 불가.
  • Kernel 이 MultiThread 인 경우
    • Kernel이 여러 스레드를 실행하므로 동기화 필요.
    • Kernel 내부에서도 별도로 세마포어 또는 동기화 메커니즘을 사용해야 함.

 

Semaphore 의 단점

  • Deadlock 이 발생할 가능성이 존재
  • P와 V의 연산이 분리되어 있기 때문에 이를 잘못 사용할 경우에 대한 대책이 없음
    • P() -> Critical Section -> P()
      • Critical Section 후에 Lock 을 풀어주는 V() 가 없으므로 한 Process가 Critical Section 에 진입하면, 나올 수 없음
        (다른 Process 들이 Critical Section 에 진입 못하므로 Deadlock 발생)
    • V() -> Critical Section -> P()
      • Critical Section 전에 Lock 을 걸어주는 P() 가 없으므로 여러 Process 가 동시에 Critical Section 에 진입할 수 있으므로, Mutual Exclusion 을 보장하지 못함
  • High Level 언어에서 동기화를 제공하는 방법이 필요해짐

 

Deadlock

  • 두 개 이상의 프로세스가 서로가 가진 자원을 기다리며, 아무도 작업을 진행하지 못하는 상태.
  • 자원(Resource)이 순환 대기(Circular Wait) 상태에 빠지면 발생.

  • $P_{1}$ 이 자원 Q 를 점유하고 자원 S 를 요청
  • $P_{2}$ 이 자원 S 를 점유하고 자원 Q 를 요청
  • $P_{1}$, $P_{2}$ 가 서로를 기다리는 상태가 발생 (Deadlock)

 

Monitor

  • High-Level 언어에서의 동기화 방법
    • Java 의 Thread 에서 동기화를 방법으로 사용되는 방식임
  • 한 순간에 하나의 Process 만 Monitor 에서 활동하도록 보장
  • Application 은 Semaphore 와 같이 P 와 V 연산에 대한 고려 없이 Procedure 를 호출하는 것만으로 동기화를 해결할 수 없음
    • Programmer 는 동기화 제약 조건 (P와 V 연산 같은 것)을 명시적으로 코드화할 필요가 없음
  • 특징
    • Entry Queue:
      • Monitor에 진입하려는 프로세스들이 대기하는 큐.
      • 하나의 프로세스만 Monitor에 진입 가능.
    • Shared Data: Monitor 내부의 공유 데이터는 오직 Monitor의 메서드(Procedure)를 통해서만 접근 가능.
    • Synchronization: Monitor는 내부적으로 동기화 문제를 해결하며, 프로세스 간 충돌 방지.
  • 문법
    • Java 나 C++ 클래스의 구문과 유사
      • 공유 변수 (shared variable):
        • Monitor 내부에서 사용하는 데이터.
      • Procedure:
        • Monitor 내부에서 공유 데이터를 사용하는 메서드.
        • 프로세스는 Monitor의 Procedure를 호출하여 작업 수행.
Monitor monitor_name {
    shared variable v;

    procedure body p1(...) {
        // Monitor에서 실행할 코드
    }

    procedure body p2(...) {
        // Monitor에서 실행할 코드
    }
    {
        // 초기화 코드
    }

    ...
}
  • 동작 과정
    • 프로세스가 Monitor 를 사용하기 위해 Entry Queue 에 진입
    • Monitor 가 비어 있으면, 해당 프로세스가 Monitor 내부로 들어가 작업 수행
    • 다른 프로세스는 Entry Queue 에서 대기
    • 작업이 완료되면 Monitor 를 나가며, 대기 중인 프로세스를 깨움

  • 동작 과정
    • 진입 대기 큐에서 Monitor로 접근:
      • 여러 프로세스가 Monitor에 접근하려고 하면, 하나의 프로세스만 Monitor 내부로 진입
      • 나머지 프로세스는 Queue of Entering Processes에서 대기
    • Monitor 내부 실행:
      • 진입한 프로세스는 필요한 Procedure를 호출하여 작업을 수행
      • 작업 중 특정 조건이 충족되지 않으면, Condition Variables(e.g., x.wait)를 사용하여 대기 상태로 들어감
    • 조건 대기 상태:
      • 조건이 충족되지 않으면, 해당 조건 변수(x.wait)의 대기 큐에 추가됨
      • 다른 프로세스가 x.signal을 호출하여 조건을 충족시키면, 대기 중인 프로세스가 깨워짐
    • 긴급 대기 큐 처리:
      • x.signal로 조건이 충족된 프로세스는 Urgent Queue로 이동
      • Urgent Queue는 Monitor 내부에서 가장 높은 우선순위를 가진다
    • Monitor 종료:
      • 작업이 완료되면 Monitor를 빠져나가며, 대기 중인 프로세스 중 하나를 깨워준다
      • Monitor는 다시 다른 프로세스에게 접근을 허용한다
  • 예시 (Transaction in Database)
    • Transaction
      • DB 내에 하나의 그룹으로 처리해야 하는 명령문들을 모아놓은 작업 단위
      • Transaction 내에 묶인 하나의 작업 단위가 반드시 완전히 수행
        • 만약 어느 하나라도 실패한다면 전체 명령문이 취소 됨

반응형