세마포어와 뮤텍스 (Semaphore, Mutex)

한눈에 보기

공유 자원 접근의 질서를 잡는 동기화 도구인 뮤텍스와 세마포어를 다룬다. 소유권 유무라는 핵심 차이, wait와 signal 연산, 생산자 소비자 문제 적용, 우선순위 역전과 상속까지 정리한다.

최종 업데이트 2026.09.17

뮤텍스(Mutex)와 세마포어(Semaphore)는 여러 실행 흐름이 공유 자원에 접근할 때 질서를 잡아 주는 대표적인 동기화 도구다. 뮤텍스는 한 번에 하나의 스레드만 임계 구역에 들어가게 하는 잠금 장치이고, 세마포어는 정수 카운터로 동시에 진입할 수 있는 수를 제어하는 더 일반적인 장치다. 상호 배제에는 뮤텍스를, 자원 개수 관리와 실행 순서 신호에는 세마포어를 쓰는 것이 기본 원칙이다.

뮤텍스의 동작

뮤텍스(mutual exclusion의 줄임말)는 lock과 unlock 두 연산을 가진다. 스레드가 lock을 호출했을 때 잠겨 있지 않으면 잠그고 진입하며, 이미 잠겨 있으면 풀릴 때까지 대기한다. unlock은 잠금을 풀고 대기 중인 스레드 하나를 깨운다. 뮤텍스의 본질적 특징은 소유권(ownership)이다. 잠근 스레드만이 풀 수 있으며, 이 규칙 덕분에 잘못된 해제를 런타임이 잡아낼 수 있고 우선순위 상속(priority inheritance) 같은 기법도 적용할 수 있다. 우선순위 상속은 낮은 우선순위 스레드가 락을 쥔 채 밀려나 높은 우선순위 스레드까지 막아 버리는 우선순위 역전(priority inversion)을 완화하기 위해, 락 소유자의 우선순위를 대기자 수준으로 잠시 끌어올리는 기법이다.

대기 방식에 따라 구현이 갈린다. 잠들어 기다리는 슬립 방식은 CPU를 낭비하지 않지만 문맥 교환 비용이 들고, 반복 확인하며 기다리는 스핀락(spinlock)은 문맥 교환이 없어 짧은 대기에 유리하지만 CPU를 태운다. 리눅스의 futex(fast userspace mutex)는 경쟁이 없을 때는 사용자 공간의 원자적 연산만으로 처리하고 경쟁이 있을 때만 커널로 들어가 잠드는 절충 구현이며, 잠깐 스핀하다가 잠드는 적응형 뮤텍스도 널리 쓰인다.

세마포어의 동작

세마포어는 음이 아닌 정수 값과 두 원자적 연산으로 정의된다. wait(P 연산, acquire)는 값이 0보다 크면 1 줄이고 진행하며, 0이면 커질 때까지 대기한다. signal(V 연산, release)은 값을 1 늘리고 대기자가 있으면 깨운다.

wait(S):                     signal(S):
  S.value -= 1                 S.value += 1
  if S.value < 0:              if S.value <= 0:
    현재 스레드를 S의             S의 대기 큐에서 하나를
    대기 큐에 넣고 잠든다          꺼내 깨운다
// 두 연산 모두 원자적으로 실행되어야 한다

초기값이 1인 이진 세마포어(binary semaphore)는 상호 배제에 쓸 수 있고, 초기값이 n인 계수 세마포어(counting semaphore)는 동일 자원 n개(예: 커넥션 풀의 연결 10개)에 대한 동시 접근을 n으로 제한한다. 세마포어에는 소유권이 없어서 어떤 스레드든 signal을 호출할 수 있는데, 이것이 약점이자 강점이다. 실수로 아무나 풀 수 있는 위험이 있지만, 한 스레드가 기다리고 다른 스레드가 신호를 보내는 순서 제어(예: 생산자가 데이터를 넣은 뒤 소비자를 깨우기)가 자연스럽게 가능하다.

뮤텍스와 세마포어 비교

구분뮤텍스세마포어
개념잠금(lock)카운터 기반 신호(signaling)
동시 진입 수항상 1초기값 n까지
소유권있음, 잠근 스레드만 해제없음, 다른 스레드가 signal 가능
주 용도임계 구역 상호 배제자원 개수 제한, 실행 순서 동기화
우선순위 역전 대책우선순위 상속 적용 가능소유자가 없어 적용 곤란
비유화장실 한 칸의 열쇠주차장 입구의 남은 자리 전광판

이진 세마포어와 뮤텍스는 동시 진입 수가 1이라는 점만 같을 뿐, 소유권 유무 때문에 서로 다른 도구다. 이 차이를 설명할 수 있는지가 면접의 단골 검증 포인트다. 또한 두 도구 모두 잘못 쓰면 교착 상태(deadlock)의 원인이 되므로, 여러 락을 잡을 때는 항상 같은 순서로 잡고 wait와 signal의 짝이 맞는지 확인하는 습관이 필요하다.

생산자 소비자 문제에 적용하기

두 도구의 역할 분담은 유한 버퍼의 생산자 소비자 문제(producer-consumer problem)에서 잘 드러난다. 빈 칸 수를 세는 세마포어 empty(초기값 N), 찬 칸 수를 세는 세마포어 full(초기값 0), 버퍼 조작을 보호하는 뮤텍스를 함께 쓴다.

생산자:                      소비자:
  wait(empty)  // 빈 칸 확보     wait(full)   // 찬 칸 확보
  lock(m)                      lock(m)
  버퍼에 삽입                    버퍼에서 꺼냄
  unlock(m)                    unlock(m)
  signal(full)                 signal(empty)

세마포어는 버퍼가 가득 찼거나 비었을 때의 대기를, 뮤텍스는 버퍼 자료구조 자체의 보호를 맡는다. wait(empty)와 lock(m)의 순서를 뒤집으면 락을 쥔 채 잠들어 교착 상태가 날 수 있다는 점도 함께 알아 두어야 한다. 현대 언어에서는 조건 변수(condition variable)와 모니터(monitor), 자바의 synchronized와 BlockingQueue, 고(Go)의 채널처럼 이 패턴을 감싼 고수준 도구를 쓰는 것이 안전하지만, 그 밑바닥 원리는 모두 여기에 있다.

면접에서 이렇게 물어본다

  • 뮤텍스와 이진 세마포어의 차이를 소유권 개념으로 설명해 보라.
  • 계수 세마포어가 필요한 실제 사례를 들고 초기값의 의미를 설명해 보라.
  • 스핀락과 슬립 방식 뮤텍스는 각각 언제 유리한가.
  • 생산자 소비자 문제를 세마포어 두 개와 뮤텍스로 설계하고 연산 순서가 왜 중요한지 말해 보라.
  • 우선순위 역전이 무엇이고 우선순위 상속이 이를 어떻게 해결하는지 설명해 보라.
운영체제 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기