컴퓨터공학

동기화 문제(Critical Section, Mutex, Semaphore)란? 개념과 해결 방법

nyambu 2025. 3. 7. 09:00

동기화 문제
동기화 문제

1. 동기화(Synchronization) 문제란?

멀티프로세스 또는 멀티스레드 환경에서는 여러 개의 프로세스(또는 스레드)가 동시에 실행되면서 동일한 자원(변수, 파일, 메모리 등)에 접근할 수 있음. 이 과정에서 데이터 충돌, 무결성 문제, 경쟁 조건(Race Condition)이 발생할 수 있으며, 이를 "동기화(Synchronization) 문제"라고 함

 

동기화 문제가 발생하면?

  • 데이터 손상: 두 개의 스레드가 동시에 같은 데이터를 수정하면, 예상하지 못한 값이 저장될 수 있음
  • 일관성 문제: 여러 개의 프로세스가 같은 파일을 동시에 변경할 때, 올바르지 않은 데이터가 기록될 수 있음
  • 무한 대기 상태: 프로세스가 서로의 작업이 끝나기를 기다리면서 교착 상태(Deadlock)에 빠질 수 있음

2. 동기화 문제의 대표적인 사례

예시 1: 은행 계좌 이체 문제

  • A가 B에게 10만 원을 송금한다고 가정
  • 두 개의 스레드가 동시에 잔액을 수정하려고 하면?
    • 스레드 1: balance = balance - 100000 (A 계좌에서 차감)
    • 스레드 2: balance = balance + 100000 (B 계좌에서 추가)
    • 실행 순서가 꼬이면, 송금이 제대로 반영되지 않는 문제 발생

예시 2: 멀티스레딩 웹 서버의 문제

  • 웹 서버에서 동시에 여러 사용자가 같은 파일을 다운로드한다고 가정
  • 동시에 파일 크기를 변경하는 연산이 발생하면?
    • 한 사용자의 파일 크기 계산이 끝나기 전에 다른 사용자의 파일이 덮어씌워질 가능성 있음

예시 3: 티켓 예매 시스템

  • 두 명의 사용자가 동시에 마지막 남은 좌석을 예매하려고 하면?
    • 스레드 1과 스레드 2가 동시에 check_available()을 실행 → 좌석이 있다고 판단
    • 두 개의 스레드가 reserve_ticket()을 실행하면서 동일한 좌석이 두 번 판매될 가능성 발생

이러한 문제를 방지하기 위해 프로세스 및 스레드 간의 동기화 기법이 필요함


3. 임계 영역(Critical Section)이란?

멀티스레딩 환경에서 여러 개의 프로세스 또는 스레드가 공유 자원(파일, 변수, 데이터베이스 등)에 접근하는 특정 코드 영역을 **"임계 영역(Critical Section)"**이라고 한다.
임계 영역에서는 동시에 하나의 프로세스(또는 스레드)만 실행되어야 하며, 그렇지 않으면 데이터 무결성 문제나 경쟁 상태(Race Condition)가 발생할 수 있음

3-1. 임계 영역 문제 발생 예시

예제 1: 은행 계좌 잔액 업데이트 오류

  1. 사용자 A가 계좌에서 10만 원을 인출하는 요청을 보냄
  2. 사용자 B가 동시에 같은 계좌에서 5만 원을 입금하는 요청을 보냄
  3. 두 요청이 동시에 실행되면서, A의 요청이 B의 요청을 덮어써 버림
  4. 최종 잔액이 올바르게 반영되지 않는 오류 발생

예제 2: 온라인 쇼핑몰 재고 시스템

  1. 고객 A와 고객 B가 마지막 남은 상품을 동시에 구매하려고 함
  2. 두 개의 프로세스가 동시에 상품 재고를 확인하는 코드(임계 영역)를 실행
  3. 시스템이 두 고객 모두에게 상품이 있다고 응답
  4. 상품은 한 개뿐이므로, 한 명만 구매할 수 있어야 하지만, 중복 결제 오류 발생

이처럼 임계 영역을 보호하지 않으면, 시스템에서 심각한 데이터 오류가 발생할 수 있음

3-2. 임계 영역 문제 해결을 위한 조건

임계 영역을 올바르게 관리하려면 다음 3가지 조건을 반드시 만족해야 함

조건 설명
1️⃣ 상호 배제(Mutual Exclusion) 한 번에 하나의 프로세스(또는 스레드)만 임계 영역을 실행할 수 있어야 함
2️⃣ 진행(Progress) 다른 프로세스가 임계 영역을 사용하지 않는다면, 대기 중인 프로세스가 실행될 수 있어야 함
3️⃣ 유한 대기(Bounded Waiting) 특정 프로세스가 무한히 기다리는 일이 없어야 하며, 공정하게 접근 기회를 가져야 함

 

이 조건이 충족되지 않으면?

  • 서로 동시에 접근하면 "데이터 충돌" 발생 (상호 배제 X)
  • 사용자가 요청을 했는데 실행되지 않는 "무한 대기 문제" 발생 (진행 조건 X)
  • 어떤 프로세스는 계속 대기하고, 특정 프로세스만 실행되는 "기아 상태(Starvation)" 발생 (유한 대기 조건 X)

3-3. 임계 영역 문제를 해결하는 기법 (전통적인 기법)

임계 영역 문제를 해결하기 위해 다음과 같은 기본적인 기법들이 사용됨

 

1) 락(Lock) 기반 동기화

  • 뮤텍스(Mutex), 세마포어(Semaphore)와 같은 동기화 도구를 사용하여 자원을 보호하는 방식
  • 프로세스가 임계 영역에 진입하기 전에 Lock을 설정하고, 사용이 끝나면 Unlock을 수행
  • 대표적인 기법: 뮤텍스(Mutex)와 세마포어(Semaphore) 활용

2) 번갈아 가며 실행하는 방식 (Turn Variable)

  • 두 개의 프로세스가 번갈아 가며 실행할 수 있도록 공유 변수(Turn Variable)를 사용하여 접근을 조절
    • 프로세스 A가 실행되면 turn = 1 설정
    • 프로세스 B는 turn이 1이 될 때까지 대기
    • A가 종료되면 turn = 2로 변경하여 B가 실행될 수 있도록 허용

3) 플래그(Flag) 기반 진입 허용 방식 (Peterson's Algorithm)

  • Peterson 알고리즘은 두 개의 프로세스가 공유 자원에 동시에 접근하지 못하도록 제어하는 기법
  • 각 프로세스는 자신이 임계 영역에 진입할 의사가 있음을 플래그로 설정하고,
    상대 프로세스가 실행 중인지 확인하여 경쟁을 방지
int flag[2] = {0, 0};
int turn;

void process_A() {
    flag[0] = 1;    // 프로세스 A가 임계 영역에 진입하려 함
    turn = 1;       // 상대 프로세스에게 실행 권한을 줄 수 있도록 설정

    while (flag[1] && turn == 1); // B가 실행 중이면 대기

    // 임계 영역 (Critical Section)
    printf("Process A is in the critical section\n");

    flag[0] = 0;    // 사용 완료 후 해제
}

 

Peterson 알고리즘의 특징

  • 단순한 소프트웨어 기반 해결책
  • 하지만 멀티코어 CPU 환경에서는 적절히 동작하지 않을 수도 있음 (메모리 일관성 문제 발생 가능)

3-4. 현대적인 임계 영역 문제 해결 기법

현대 운영체제에서는 더 강력하고 안정적인 동기화 도구를 활용하여 임계 영역 문제를 해결함

 

1) 스핀락(Spinlock) 활용

  • Lock이 해제될 때까지 계속 루프를 돌면서 기다리는 방식
  • 단순한 방식이지만, CPU 자원을 많이 소모하는 단점이 있음
  • 멀티코어 환경에서 사용됨 (대기 시간이 짧은 경우 적합)

2) 조건 변수(Condition Variable) 활용

  • 특정 조건이 만족될 때까지 스레드를 대기 상태로 유지할 수 있는 기법
  • ex: 생산자-소비자 문제에서 버퍼가 가득 찰 경우 소비자가 소비할 때까지 생산자를 대기 상태로 만듦

3) 원자적 연산(Atomic Operation) 사용

  • CPU에서 제공하는 "Atomic Instruction(원자 연산)"을 사용하여 데이터 경쟁을 방지
  • 예: Compare-And-Swap(CAS), Fetch-And-Add 등의 기법

4) 트랜잭션 메모리(Transactional Memory) 활용

  • 메모리 트랜잭션을 사용하여 동기화 문제를 해결하는 최신 기술
  • 만약 트랜잭션이 실패하면 자동으로 롤백(Undo) 수행

4. 동기화 문제 해결 기법

멀티스레드 환경에서 동기화 문제를 해결하기 위해 세마포어(Semaphore), 뮤텍스(Mutex), 모니터(Monitor) 등의 기법이 사용됨

 

4-1. 뮤텍스(Mutex)란? (Mutual Exclusion Lock)

뮤텍스란?

  • 한 번에 하나의 프로세스 또는 스레드만 특정 코드(임계 영역)에 접근하도록 제한하는 방법
  • Lock(잠금)을 걸고, 사용이 끝나면 Unlock(해제)을 해야 함

📌 뮤텍스 사용 예시

pthread_mutex_t lock;

void critical_section() {
    pthread_mutex_lock(&lock);  // 뮤텍스 잠금
    // 공유 자원 접근 (임계 영역)
    pthread_mutex_unlock(&lock);  // 뮤텍스 해제
}

 

뮤텍스의 특징

  • 한 번에 한 개의 스레드만 접근 가능
  • 다른 스레드는 대기 상태로 전환됨
  • 자원을 사용한 후에는 반드시 잠금 해제(Unlock)해야 함

4-2. 세마포어(Semaphore)란?

세마포어란?

  • 여러 개의 프로세스(또는 스레드)가 제한된 개수의 자원을 사용할 수 있도록 허용하는 기법
  • 카운팅 방식으로 자원 접근을 제어함
    • Binary Semaphore (이진 세마포어, 뮤텍스와 유사)
    • Counting Semaphore (카운팅 세마포어, 여러 개의 프로세스를 허용)

📌 세마포어 사용 예시 (C 코드)

#include <semaphore.h>

sem_t semaphore;

void critical_section() {
    sem_wait(&semaphore);  // 자원 획득 (P 연산)
    // 공유 자원 접근
    sem_post(&semaphore);  // 자원 반납 (V 연산)
}

 

세마포어의 특징

  • 여러 개의 스레드가 동시에 실행될 수 있도록 허용 가능
  • 리소스를 효율적으로 관리할 수 있음
  • 다만, 세마포어를 잘못 설정하면 데드락(Deadlock)이 발생할 수도 있음

4-3. 모니터(Monitor)란?

모니터란?

  • 뮤텍스 + 조건 변수(Condition Variable)를 함께 사용하는 방식
  • 운영체제에서 언어 차원에서 제공되는 동기화 기법 (예: Java의 synchronized 블록)

📌 Java 모니터 사용 예시

class SharedResource {
    synchronized void critical_section() {
        // 공유 자원 접근 (임계 영역)
    }
}

 

모니터의 특징

  • 자바(Java)와 같은 객체지향 언어에서 사용
  • 운영체제가 자동으로 Lock 및 Unlock을 관리
  • 코드가 간결하지만, 성능 오버헤드가 발생할 수 있음

5. 동기화 문제 해결을 위한 선택 기준

기법 설명 적용 사례특징
뮤텍스(Mutex) 한 번에 하나의 프로세스(스레드)만 접근 가능 파일 쓰기, 은행 계좌 업데이트 Lock / Unlock 필요
세마포어(Semaphore) 제한된 개수의 자원을 공유 티켓 예매 시스템, 네트워크 연결 P/V 연산 사용, 다중 접근 가능
모니터(Monitor) 뮤텍스 + 조건 변수 조합 Java synchronized 블록 운영체제가 Lock 관리