운영체제#4 동기화

성찬홍·2025년 12월 7일

Computer Science

목록 보기
14/22

1. 동기화

동시다발적으로 실행되는 프로세스들은 서로 협력하며 영향을 주고받는다.
이 과정에서 자원의 일관성을 보장해야 한다.
→ 프로세스나 스레드는 동시에 실행되면서 공유 자원(변수, 파일, 메모리 등)을 사용한다.

  • 공동의 목적을 위해 동시에 수행되는 프로세스이다.
  • 여러 프로세스나 스레드가 공유 자원에 접근할 때 서로 간섭하지 않도록 실행 순서를 조정하는 것을 의미한다.
  • 쉽게 말해 공유 데이터를 안전하게 사용하기 위한 규칙이라고 보면 된다.

& 프로세스들의 수행 시기를 맞추는 것

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

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

: “스레드 또는 프로세스가 특정 순서대로 실행되도록 강제하는 기술”을 의미한다.

Ex)

Writer: Book.tsx 파일에 값을 저장하는 프로세스
Reader: Book.tsx 파일을 읽는 프로세스

→ Reader와 Writer 프로세스는 무작정 아무렇게나 실행되어서는 안 된다.
→ 실행 순서가 있기 때문이다.
Reader 프로세스는 ‘Book.tsx 안에 값이 존재한다’는 특정 조건이 만족되어야만 실행이 가능하다.

상호 배제를 위한 동기화

  • 공유가 불가능한 자원의 동시 사용을 피하기 위한 동기화
  • 한 번에 하나의 프로세스만 접근해야 하는 자원에 대한 동시 접근을 피하기 위한 동기화

Ex) 문제가 생기는 경우 예시

공유 자원

: 여러 프로세스 혹은 스레드가 공유하는 자원

→ 전역 변수, 파일, 입출력 장치, 보조 기억 장치 등

임계 구역

: 동시에 실행되면 문제가 발생하는 자원에 접근하는 코드 영역

→ 여러 스레드가 동시에 접근하면 문제가 발생하는 코드 영역

→ 반드시 한 번에 하나의 스레드만 실행해야 한다.

→ 앞선 예시의 ‘총합’ 변수, ‘잔액’ 변수 등

→ 임계 구역에 진입하고자 하면 진입한 프로세스 이외에는 대기해야 한다.

→ 임계 구역에 동시에 접근하면 자원의 일관성이 깨질 수 있다.

→ 이를 레이스 컨디션이라고 한다.

운영체제가 임계 구역 문제를 해결하는 세 가지 원칙

  • 상호 배제

    • 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 들어올 수 없다.
  • 진행

    • 임계 구역에 어떤 프로세스도 진입하지 않았다면 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
  • 유한 대기

    • 한 프로세스가 임계 구역에 진입하고 싶다면 언젠가는 임계 구역에 들어올 수 있어야 한다.
    • 임계 구역에 들어가기 위해 무한정 대기해서는 안 된다.

동기화 기법

뮤텍스 락

: 상호 배제를 위한 동기화 도구(자물쇠 역할), 뮤텍스 락

→ 공유 자원에 동시에 접근하는 스레드들 사이의 충돌을 막기 위해 사용되는 동기화 도구

오직 하나의 스레드만 접근할 수 있도록 보장하는 기법

→ 전역 변수 하나, 함수 두 개

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

acquire 함수

  • 프로세스가 임계 구역에 진입하기 전에 호출

  • 임계 구역이 잠겨 있다면

    • 임계 구역이 열릴 때까지(lockfalse가 될 때까지) 임계 구역을 반복적으로 확인
  • 임계 구역이 열려 있다면

    • 임계 구역을 잠그기(locktrue로 바꾸기)

release 함수

  • 임계 구역에서의 작업이 끝나고 호출

  • 현재 잠긴 임계 구역을 열기(lockfalse로 바꾸기)

  • 바쁜 대기

while (lock == true) /* 만약 임계 구역이 잠겨 있다면 */
  ;                  /* 임계 구역이 잠겨 있는지를 반복적으로 확인 */
     Thread A                    Thread B
   -------------              ---------------
   lock(mutex)     ----lock(mutex)  (대기)
   임계 구역 실행                (기다림)
   unlock(mutex)   ----→      lock 획득 → 임계 구역 실행

세마포어

→ 정수 변수(S)로 표현되는 동기화 기법이며, wait(P)와 signal(V) 연산으로 자원 접근을 제어한다.

  • 좀 더 일반화된 방식의 동기화 도구
  • 공유 자원이 여러 개 있는 경우에도 적용 가능
  • 임계 구역 앞에서 멈춤 신호를 받으면 잠시 기다리기
  • 임계 구역 앞에서 가도 좋다는 신호를 받으면 임계 구역 진입

→ 매번 임계 구역 앞뒤로 wait(), signal()을 호출하기에는 사용자 입장에서 불편함.

세마포어의 종류

(1) 이진 세마포어(Binary Semaphore)

  • 값: 0 또는 1

  • 사실상 뮤텍스와 유사한 상호 배제 역할

  • 단, 소유권이 없음 → 아무 스레드나 signal 가능

    → 뮤텍스와의 중요한 차이점!

(2) 카운팅 세마포어(Counting Semaphore)

  • 값: 0 이상 N

  • 자원이 여러 개 있을 때 사용

    예: DB connection pool, 주차장, 스레드 풀 등

    N개의 자원을 동시에 최대 N명이 사용

  • 전역 변수 S: 임계 구역에 진입할 수 있는 프로세스의 개수를 나타내는 변수

  • wait(): 자원 사용 요청

    • 임계 구역에 들어가도 좋은지, 기다려야 할지 알려 주는 함수
    • S > 0이면 S를 하나 줄이고 입장
    • S = 0이면 자원이 없으므로 대기(block)
  • signal(): 자원 사용 완료

    • 임계 구역 앞에서 기다리는 프로세스에 ‘이제 가도 좋다’를 알려 주는 함수

ex) 레스토랑에 테이블이 3개 있다면

S = 3  (테이블 3)

손님1wait()S=2 → 식사 시작  
손님2wait()S=1 → 식사 시작  
손님3wait()S=0 → 식사 시작  
손님4wait()S=0 → 테이블 없음 → 대기

손님1이 떠나면

signal()S=1 → 대기 중인 손님4가 식사 시작

코드 예시

class Semaphore {
  constructor(max) {
    this.max = max;           // 동시에 실행 가능한 개수
    this.current = 0;         // 현재 실행 중인 작업 수
    this.queue = [];          // 대기 중인 resolve 함수들
  }

  async acquire() {
    if (this.current < this.max) {
      this.current++;
      return Promise.resolve();
    }

    // 대기 상태 → resolve가 push되어 signal(= release) 때 호출됨
    return new Promise((resolve) => this.queue.push(resolve));
  }

  release() {
    // 대기 중인 작업이 있다면 깨워 주기
    if (this.queue.length > 0) {
      const resolve = this.queue.shift();
      resolve();
    } else {
      this.current--;
    }
  }
}

사용

const semaphore = new Semaphore(2);

async function task(name, delay) {
  await semaphore.acquire(); // wait
  
  console.log(`${name} 시작`);
  await new Promise((r) => setTimeout(r, delay));
  console.log(`${name} 종료`);

  semaphore.release(); // signal
}

async function run() {
  task("A", 2000);
  task("B", 1000);
  task("C", 500);
  task("D", 800);
}

run();

모니터

: 상호 배제를 위한 동기화와 실행 순서 제어를 위한 동기화 두 가지를 모두 제공한다.

상호 배제를 위한 동기화

  • 인터페이스를 위한 큐
  • 공유 자원에 접근하고자 하는 프로세스를 인터페이스를 위한 큐에 삽입
  • 큐에 삽입된 순서대로 한 번에 하나의 프로세스만 공유 자원을 이용

정리

동기화란 여러 프로세스나 스레드가 동시에 실행되며 공유 자원(변수, 파일, 메모리 등)을 사용할 때 서로 간섭하지 않도록 실행 순서와 접근 방식을 조정하는 것을 의미한다. 이를 통해 데이터의 일관성을 보장하며, 실행 순서를 강제하거나(예: Reader–Writer 문제) 동시에 접근해서는 안 되는 자원에 한 번에 하나의 프로세스만 접근하도록 제한하는 상호 배제(Mutual Exclusion)를 달성한다. 특히 임계 구역(critical section)에서 발생할 수 있는 레이스 컨디션을 방지하기 위해 운영체제는 상호 배제, 진행, 유한 대기라는 세 가지 원칙을 통해 임계 구역 문제를 해결한다.

이러한 동기화를 구현하기 위해 다양한 기법이 사용되는데, 대표적으로 뮤텍스(Mutex)와 세마포어(Semaphore)가 있다. 뮤텍스는 단 하나의 스레드만 자원에 접근할 수 있도록 잠금(lock)을 제공하는 반면, 세마포어는 정수 값을 사용해 여러 개의 자원을 동시에 관리할 수 있어 더 일반적인 형태의 동기화 도구이다. 세마포어는 이진(Binary) 형태로 뮤텍스와 유사하게 사용되거나, 카운팅 형태로 자원이 여러 개일 때 wait/signal 연산을 통해 접근을 제어한다. 또한 모니터(Monitor)는 상호 배제와 실행 순서 제어 기능을 모두 제공하는 고수준 동기화 도구로, 내부 큐를 통해 공유 자원 접근을 자동으로 관리하여 보다 안전하고 간편한 동기화 환경을 제공한다.

참고

https://www.inflearn.com/course/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C/dashboard

profile
꾸준한 개발자

0개의 댓글