기술 면접 준비

Critical Section / 세마포어(Semaphore) / 뮤텍스(Mutex)

jinsang-2 2025. 1. 15. 13:50

임계 영역(Critical Section)

  • 임계 구역(Critical Section)은 공유 자원(Shared Resource)에 여러 스레드나 프로세스가 동시에 접근할 경우 문제가 발생할 수 있는 프로그램의 코드 영역을 의미한다.

❗문제 발생 

  • 공유 자원은 여러 프로세스/스레드가 동시에 접근할 경우 임계 구역 안에서 데이터 불일치 또는 경쟁 상태(Race Condition)를 초래할 수 있다.
  • 임계 구역 문제를 해결하려면 한 번에 하나의 스레드만 해당 코드 영역에 진입하도록 제한해야 한다.

🛠️ 해결 방법

  • 뮤텍스(Mutex), 세마포어(Semaphore) 등의 동기화 메커니즘을 사용하여 한 번에 하나의 스레드만 임계 구역에 접근하도록 보장한다.

💡 이해하기 쉬운 예시

은행 계좌에서 두 개의 ATM 스레드가 동시에 withdraw() 함수를 호출한다면?

int balance = 1000;

void withdraw(int amount) {
    if (balance >= amount) {
        balance -= amount;  // 임계 구역
    }
}
  • ATM1: withdraw(800) 호출
  • ATM2: withdraw(500) 호출

만약 임계 구역 보호가 없으면 두 스레드가 동시에 if (balance >= amount) 조건을 확인한 뒤, balance를 각각 800, 500으로 줄여 -300 같은 불일치한 결과가 나올 수 있음

이를 방지하려면 뮤텍스세마포어를 이용해 임계 구역을 보호해야 한다.


세마포어 (Semaphore)

세마포어(Semaphore)는 공유 자원에 대한 접근을 제어하기 위해 사용되는 동기화 도구이다. 

프로세스가 자원을 요청하거나 해제할 때 이를 정수 값을 이용해 관리한다. 

  • 세마포어는 정수 값(카운터)로 초기화되며, 이 값은 동시에 자원에 접근 가능한 스레드의 수를 나타낸다.
  • 자원을 사용하려는 스레드는 P(wait) 연산(혹은 down 연산)을 호출하여 세마포어 값을 1 감소시킴
  • 작업을 완료한 스레드는 V(signal) 연산(혹은 up 연산)을 호출하여 세마포어 값을 1 증가시킨다.
  • 세마포어 값이 0이면, 새로운 스레드는 대기 상태에 들어가며, 자원이 해제될 때까지 기다린다.

📌 타입

  • 이진 세마포어(Binary Semaphore)
    • 세마포어 값이 0 또는 1만 가질 수 있다
    • 동작 방식이 뮤텍스와 유사하며, 단일 자원의 동기화를 위해 사용 된다.
  • 카운팅 세마포어(Counting Semaphore)
    • 세마포어 값이 0 이상의 정수 값을 가질 수 있다.
    • 동시에 접근 가능한 자원의 개수를 나타낸다.

💡 예시

  • 공항의 3대의 보안 스캐너를 생각해봅시다. 세마포어 값을 3으로 초기화하고, 승객이 도착할 때마다 세마포어 값을 P() 연산으로 줄인다.
  • 스캐너 사용이 끝나면 V() 연산으로 세마포어 값을 증가시켜 다음 승객이 사용할 수 있도록 함

뮤텍스(Mutex : Mutual Exclusion)

뮤텍스는 임계 영역(Critical Section)을 보호하기 위해 사용되는 동기화 도구이다. 

  • Binary Semaphore와 유사하지만, 뮤텍스는 스레드 소유권(Ownership)이 있다.
  • 공유된 자원은 한 번에 한 프로세스(스레드)만이 사용할 수 있어야 한다.
  • key에 해당하는 어떤 오브젝트가 있으며 이 오브젝트를 소유한 (쓰레드, 프로세스) 만이 공유자원에 접근할 수 있다.
  • 뮤텍스 객체를 두 스레드가 동시에 사용할 수 없다!

💡 예시

흔히 뮤텍스는 키(열쇠)가 하나뿐인 화장실을 예로 들어서 설명한다.

화장실을 이용하기 위해서는 열쇠를 카운터에서 받아 가야 하고, 누군가 화장실 키를 가지고 사용하고 있으면 다른 사람들은 카운터에서 기다려야(대기) 함

화장실을 이용하는 사람은 프로세스 혹은 스레드이며 화장실은 공유자원, 화장실 키는 공유자원에 접근하기 위해 필요한 어떤 오브젝트이다. 

세마포어와 뮤텍스 차이점

특징 뮤텍스 세마포어
동시 접근 가능 수 하나의 스레드만 여러 스레드 동시 접근 가능
소유권(Ownership) 락을 획득한 스레드만 락 해제 가능 특정 스레드에 소유권이 없음
용도 상호 배제(Mutual Exclusion) 제한된 동시성(Concurrency Control)
초기화 값 초기값은 1 (0과 1만 가지는 이진 상태) 초기 값은 0 이상의 정수
해제 가능 주체 락을 소유한 스레드만 해제 가능 모든 스레드가 세마포어 값을 증가/ 감소 가능
교착 상태 교착 상태 발생 가능 교착 상태 또는 기아 상태 발생 가능
사용 난이도 상대적으로 간단 상대적으로 복잡

 

🤔2개의 자원 접근 통제는 이진 세마포어만 사용하면 되는거 아닌가??

뮤텍스는 특정 리소스를 한 번에 하나의 스레드만 사용할 수 있도록 보장하는 데 적합하다. 예를 들어, 은행 계좌를 업데이트하는 상황에서 두 스레드가 동시에 접근하면 데이터 무결성이 깨질 수 있으므로, 뮤텍스를 사용하여 이를 방지할 수 있다.

이진 세마포어는 소유권이 없어서 생산자-소비자 문제와 같은 경우에는 사용할 수 있지만, 한 스레드가 잠금을 설정하고 다른 스레드가 실수로 해제할 경우 동기화가 깨지는 문제가 발생할 수 있음.

따라서 상호 배제가 필요하다면 뮤텍스를 사용하는 것이 더 안전하고 명확한 선택이다!!

 

참고) 생산자 - 소비자 문제

예를 들어 멀티 스레드 기반 웹서버일 때 요청을 받기 위한 대기(listen)하는 쓰레드가 다수 존재할 때 요청이 들어오면 해당 요청을 받은 쓰레드가 그 요청(httpRequest)를 대기큐에 쌓아 놓아둔다. 또 요청을 처리하기 위한 다수의 스레드가 존재해, 대기큐에 있는 요청을 하나씩 꺼내서 처리한다. 

여기서 생산자는 웹 서버 요청을 받아 대기큐에 쌓는 쓰레드를 의미하고 소비자는 요청을 처리하기 위해 기다리면서, 대기큐에 요청이 있으면 하나씩 꺼내는 스레드이다. 여기서 대기큐가 공유 자원이다. 

생산자와 소비자가 동시에 대기큐(공유자원)에 접근할 시에 경쟁 상태(race condition) 문제가 발생할 수 있기에 동기화 기법을 사용해야 한다.