이 장을 읽기 전에
레이스 컨디션과 락에서 뮤텍스가 “값이 0 또는 1이라 한 번에 한 스레드만 임계 구역에 들어갈 수 있는” 락이라고 다뤘다. 이 챕터는 그 값이 1이 아니라 임의의 정수일 때 어떤 일이 벌어지는지를 다룬다 — 뮤텍스를 이미 이해했다면 세마포어는 그 개념의 자연스러운 일반화다.
왜 “한 명만"이 아니라 “N명까지"가 필요한가
뮤텍스는 임계 구역에 정확히 한 스레드만 들어가게 강제한다. 하지만 실무에서는 “동시에 최대 N개까지만 허용"해야 하는 상황이 더 흔하다. 데이터베이스 커넥션 풀이 대표적이다 — 커넥션을 무제한으로 열면 데이터베이스 서버가 감당하지 못하므로, “최대 10개 커넥션까지만 동시 사용 허용"이라는 제약을 걸어야 한다. 뮤텍스로는 이 제약을 표현할 수 없다. 값이 0과 1 사이만 오가기 때문이다.
세마포어(Semaphore)는 이 문제를 위해 1965년 에츠허르 데이크스트라(Edsger Dijkstra)가 고안한 동기화 도구로, 내부에 음수가 아닌 정수 카운트를 갖는다. 스레드가 자원을 쓰려면 wait(또는 P 연산, POSIX에서는 sem_wait)를 호출해 카운트를 1 감소시키고, 카운트가 이미 0이면 다른 스레드가 signal(V 연산, sem_post)로 카운트를 늘릴 때까지 대기한다. 카운트의 초기값이 곧 “동시에 몇 개까지 허용할지"를 결정한다 — 초기값을 1로 두면 뮤텍스와 동일하게 동작하므로, 뮤텍스는 세마포어의 특수한 경우(이진 세마포어)로 볼 수 있다.
| |
gcc -pthread semaphore_pool.c -o semaphore_pool로 컴파일해 실행하면, 6개 워커 중 항상 최대 3개만 “커넥션 획득” 상태에 동시에 있고 나머지는 슬롯이 반납될 때까지 대기한다. 세마포어의 카운트는 뮤텍스처럼 “누가 락을 쥐고 있는지"를 추적하지 않고 “남은 슬롯 수"만 추적한다는 점이 중요하다 — 그래서 락을 건 스레드가 아닌 다른 스레드가 sem_post를 호출해도 문제가 없고, 이 특성은 생산자-소비자 패턴에서 “빈 슬롯 수"와 “채워진 슬롯 수"를 각각 별도의 세마포어로 표현하는 데 활용된다.
모니터: 락과 조건 변수를 한데 묶기
세마포어는 강력하지만, wait/signal 호출을 어디에 놓을지 프로그래머가 직접 관리해야 하므로 실수하기 쉽다. 모니터(Monitor)는 이 관리를 언어·런타임 수준으로 끌어올린 고수준 동기화 도구로, 1974년 토니 호어(C. A. R. Hoare)와, 그보다 앞서 1972~1973년 저서 『Operating System Principles』에서 유사한 개념을 완성한 퍼 브린치 한센(Per Brinch Hansen)이 독립적으로 정식화했다. 모니터는 공유 데이터, 그 데이터를 다루는 메서드, 그리고 그 메서드들을 감싸는 락을 하나의 단위로 캡슐화한다 — 모니터 안의 메서드는 항상 한 스레드만 실행할 수 있고, 메서드가 끝나면 락이 자동으로 풀린다.
모니터는 여기에 더해 조건 변수(Condition Variable)를 제공해 “지금은 조건이 안 맞으니 기다렸다가, 조건이 맞춰지면 다시 검사"하는 패턴을 표현한다. POSIX의 pthread_mutex_t + pthread_cond_t 조합이 모니터의 대표적인 구현이고, Java의 synchronized 키워드와 wait()/notify()도 같은 개념을 언어 문법으로 내장한 것이다.
| |
pthread_cond_wait는 대기에 들어가는 순간 락을 자동으로 풀고, 깨어날 때 락을 다시 잡는다는 점이 핵심이다 — 이 원자적 처리가 없으면 “락을 푼 직후, 대기에 들어가기 직전” 사이의 틈에서 신호를 놓치는 버그(lost wakeup)가 생긴다. if가 아니라 while로 조건을 재검사하는 이유는 가짜 기상(Spurious Wakeup) 때문이다 — 신호가 없었는데도 대기 스레드가 깨어날 수 있어, 깨어난 직후 조건을 다시 확인하지 않으면 조건이 거짓인 채로 진행해버릴 수 있다.
| 항목 | 세마포어 | 모니터(뮤텍스+조건 변수) |
|---|---|---|
| 핵심 상태 | 정수 카운트 | 락 상태 + 조건 큐 |
| “누가 풀었는지” 추적 | 안 함(카운트만 관리) | 락 소유자 개념 있음 |
| 동시 진입 허용 수 | 카운트로 N개까지 표현 가능 | 기본은 1개(추가 카운트 필요 시 직접 구현) |
| 대표 API | sem_wait/sem_post | pthread_mutex_lock/unlock + pthread_cond_wait/signal |
| 대표 활용 | 커넥션 풀, 리소스 제한 | 생산자-소비자 큐, 상태 기반 대기 |
흔한 오개념
“세마포어는 뮤텍스보다 항상 안전하다” — 세마포어의 카운트는 소유권 개념이 없어서, 락을 걸지 않은 스레드가 실수로 sem_post를 두 번 호출해도 컴파일러나 런타임이 막지 못한다. 이 경우 카운트가 실제 자원 수보다 많아져 허용 한도를 넘는 동시 접근이 생길 수 있다. 뮤텍스는 (구현에 따라) 락을 건 스레드만 풀 수 있도록 강제하는 경우가 많아 이런 실수를 방지하는 데는 오히려 더 유리하다.
“조건 변수는 조건을 직접 저장한다” — pthread_cond_t는 조건 자체를 갖고 있지 않고, 단지 “이 조건과 관련된 대기 스레드들을 깨우는 신호 채널"일 뿐이다. 실제 조건(queue_size == 0 같은)은 항상 락으로 보호되는 별도의 변수로 프로그래머가 직접 관리해야 하고, 조건 검사는 항상 락을 쥔 상태에서 이뤄져야 한다.
다른 개념과의 연결
레이스 컨디션과 락에서 다룬 뮤텍스는 이 챕터에서 세마포어의 카운트가 1인 특수 사례이자, 모니터를 구성하는 두 요소 중 하나로 다시 등장했다. 다음 챕터에서는 뮤텍스와 세마포어처럼 스레드를 아예 대기시키는 대신, 락 없이 하드웨어 수준에서 값을 원자적으로 바꾸는 CAS(Compare-And-Swap) 기법을 다룬다.
평가 기준
이 챕터를 읽은 후에는 다음을 할 수 있어야 한다. 세마포어의 카운트가 뮤텍스의 0/1 값을 어떻게 일반화하는지 설명할 수 있다. 커넥션 풀처럼 “동시에 N개까지 허용"해야 하는 상황에 세마포어를 적용할 수 있다. 모니터가 락과 조건 변수를 캡슐화하는 방식과, while로 조건을 재검사해야 하는 이유(가짜 기상)를 설명할 수 있다.
참고 자료
Hoare, C. A. R. (1974). “Monitors: An Operating System Structuring Concept”. Communications of the ACM, 17(10), 549–557.
- POSIX.1-2017: semaphore.h — POSIX 세마포어 API 표준 정의
- Dijkstra, E. W. (1968). “Cooperating Sequential Processes” (EWD123) — 세마포어를 최초로 정식화한 원문 노트
![Featured image of post [Computer Terms] 세마포어와 모니터 (Semaphore, Monitor)](/post/computerterms/semaphores-and-monitors/wordcloud_hu_cd2d8ae5c02d39c8.webp)
![[Computer Terms] 메시지 큐 (Message Queue)](/post/computerterms/message-queues/wordcloud_hu_dbe38b4ba7c3dd1e.webp)
![[Computer Terms] 이벤트 소싱 (Event Sourcing)](/post/computerterms/event-sourcing/wordcloud_hu_f5b285e5f90fbf41.webp)
![[Computer Terms] 세마포어와 모니터 (Semaphore, Monitor)](/post/computerterms/semaphores-and-monitors/wordcloud_hu_10f283df96ac90d1.webp)
![[Computer Terms] 원자적 연산과 CAS (Atomic Operations, Compare-And-Swap)](/post/computerterms/atomic-operations-and-cas/wordcloud_hu_ebcc727cf2f5a53a.webp)
![[Computer Terms] 코루틴과 async/await (Coroutine, async/await)](/post/computerterms/coroutines-and-async-await/wordcloud_hu_dc1ca1fb4cccb7f9.webp)
![[Computer Terms] ABA 문제 (ABA Problem)](/post/computerterms/aba-problem/wordcloud_hu_4b6d7ba1d5a7dedf.webp)
![[Computer Terms] 데드락 (Deadlock)](/post/computerterms/deadlocks/wordcloud_hu_1aa2cb817735e0c9.webp)
![[Computer Terms] 레이스 컨디션과 락 (Race Condition, Lock)](/post/computerterms/race-conditions-and-locks/wordcloud_hu_616c5d17f3890a88.webp)
![[Computer Terms] 시그널 (Signal)](/post/computerterms/signals/wordcloud_hu_14499a49bfbab5c1.webp)