5편. 프로세스 동기화 완전 정복 – 경쟁 조건과 뮤텍스, 세마포어, 모니터
📚 목차
1. 왜 동기화가 필요한가? - 실무 오류를 부르는 충돌의 시작
2. 경쟁 조건(Race Condition)이란 - 순서만 달라졌을 뿐인데 벌어지는 일
3. 임계 구역(Critical Section)이란 - 충돌을 막기 위한 통제 구간
4. 동기화 3대장 - 뮤텍스, 세마포어, 모니터 쉽게 이해하기
✔ 마무리 - 동기화는 실무에서 자주 마주치는 기본기입니다.
브라우저로 유튜브를 틀어 놓고, 동시에 문서를 편집하며, 메신저로 친구와 대화하는 것이 일상이 되었죠. 이처럼 동시에 여러 작업을 처리하는 능력, 즉 ‘동시성(Concurrency)’은 현대 운영체제의 기본 전제가 되었습니다.
하지만 여기엔 보이지 않는 위험이 숨어 있습니다.
여러 프로세스나 스레드가 같은 자원에 동시에 접근하게 되면, 타이밍에 따라 결과가 예기치 않게 꼬일 수 있습니다. 아무 문제없어 보이던 코드가 실제 환경에서는 이상하게 동작하고, 데이터를 잃거나 시스템이 멈추는 일이 발생합니다.
이러한 문제는 프로세스 동기화(Process Synchronization) 없이 절대 해결되지 않습니다.
이 글에서는 왜 동기화가 필요한지, 어떤 문제가 생기는지, 그리고 이를 해결하기 위해 운영체제와 프로그래밍 언어가 제공하는 뮤텍스, 세마포어, 모니터 같은 핵심 기법들을 실제 시나리오와 함께 알아봅니다.
1. 왜 동기화가 필요한가? – 실무 오류를 부르는 충돌의 시작
오늘날 대부분의 운영체제는 멀티코어 CPU와 멀티태스킹 환경을 기본으로 제공합니다. 이로 인해 여러 프로세스와 스레드가 동시에 실행될 수 있어, 사용자 입장에서는 더욱 빠르고 효율적인 작업 환경을 누릴 수 있게 되었습니다.
하지만 이러한 동시성(Concurrency) 환경은 편리함만을 제공하지 않습니다. 여러 실행 단위가 동시에 하나의 자원에 접근하게 되면, 미묘한 타이밍 차이로 인해 심각한 오류가 발생할 수 있습니다. 이 문제를 해결하지 않으면 데이터가 꼬이거나 손실되고, 시스템이 예기치 않게 오작동하게 됩니다.
✔️ 예시 시나리오: 은행 계좌의 이중 인출 문제
한 사용자가 ATM과 모바일 앱을 통해 동시에 1만 원을 인출한다고 가정해 봅시다. 계좌의 초기 잔액은 5만 원입니다.

한 고객이 ATM에서 1만 원을 인출하고, 동시에 모바일 앱으로 다른 계좌에 1만 원을 송금하려고 한다고 가정해 봅시다. 고객은 두 거래를 거의 같은 시각에 진행했으며, 두 요청은 각기 다른 경로로 은행 서버에 전달됩니다.
이때 은행 서버의 계좌 잔액은 50,000원입니다.
두 거래가 순차적으로 처리된다면 다음과 같은 계산이 이뤄져야 합니다:
초기 잔액: 50,000원
1차 거래(ATM 인출): 50,000 - 10,000 = 40,000
2차 거래(모바일 송금): 40,000 - 10,000 = 30,000
즉, 정상적인 최종 잔액은 30,000원이어야 합니다. 하지만 동기화 처리가 제대로 되어 있지 않은 시스템에서는 다음과 같은 문제가 발생할 수 있습니다:
✔️ 문제 발생 과정 – 타이밍이 만들어낸 오류
1. ATM과 모바일 앱 양쪽 모두 서버로부터 현재 잔액(50,000원)을 조회합니다.
→ 이 시점에서는 아직 어떤 쪽도 잔액을 변경하지 않았습니다.
2. ATM이 먼저 처리되어, 50,000원에서 10,000원을 인출하고, 갱신된 값 40,000원을 서버에 저장합니다.
3. 그 직후, 모바일 앱 요청도 자신이 미리 읽어두었던 50,000원을 기반으로 계산하여 역시 40,000원을 서버에 덮어씌웁니다.
📌 최종 결과: 잔액은 40,000원으로 남게 됩니다.
문제점: 실제로는 1만 원이 두 번 인출되었지만, 한 번만 반영된 것처럼 보이는 심각한 오류입니다.
이처럼 동시에 실행된 두 개의 작업이 서로 독립적으로 동작하면서도, 같은 데이터를 공유하는 상황에서 타이밍에 따라 결과가 달라질 수 있습니다. 이 현상을 운영체제에서는 경쟁 조건(Race Condition)이라고 부릅니다.
즉, “프로그램 로직에는 아무 문제가 없어 보이지만, 실행 시점의 순서와 타이밍에 따라 결과가 망가지는 것”이 경쟁 조건의 본질입니다.
이러한 문제는 은행 시스템뿐 아니라, 쇼핑몰의 재고 처리, 예약 시스템, 온라인 게임의 상태 저장 등 다양한 실무 환경에서 실제로 발생하며, 동기화 없이 처리할 경우 큰 손실이나 오류로 이어질 수 있습니다.
2. 경쟁 조건이란? – 순서만 달라졌을 뿐인데 벌어지는 일
멀티코어 환경에서 여러 프로세스 또는 스레드가 동시에 실행되다 보면, 특정 자원에 대해 누가 먼저 접근하느냐에 따라 프로그램의 결과가 달라지는 문제가 발생할 수 있습니다.
이를 운영체제에서는 경쟁 조건(Race Condition)이라고 부릅니다
🔷 경쟁 조건(Race Condition)이란?
경쟁 조건(Race Condition)은 둘 이상의 프로세스(또는 스레드)가 하나의 공유 자원에 동시에 접근하면서,
실행 순서에 따라 결과가 달라지는 비결정적인 현상을 말합니다.
이 문제의 핵심은 다음과 같습니다:
🔸 각 프로세스는 개별적으로는 정상적으로 작동합니다.
🔸 그러나 동시에 실행될 때, 예상하지 못한 결과를 만들어냅니다.
🔸 즉, 타이밍이 모든 것을 좌우하게 됩니다.
이러한 상황은 테스트에서는 잘 작동하다가도, 실제 환경에서 수시로 문제를 일으키는 잠복 오류로 작용할 수 있어 매우 위험합니다.
🔷 경쟁 조건이 발생하는 3가지 조건
경쟁 조건은 단순히 동시에 실행된다고 해서 항상 발생하는 것이 아닙니다. 다음 3가지 조건이 모두 충족될 때 발생합니다
🔸 ① 공유 자원 접근 – 같은 자원을 함께 쓰려할 때
경쟁 조건은 우선 여러 실행 단위(프로세스나 스레드)가 하나의 자원을 함께 사용할 때 시작됩니다. 이 자원은 변수 하나일 수도 있고, 파일, 메모리, 데이터베이스, 네트워크 포트 등 무엇이든 될 수 있습니다.
예를 들어, 두 명의 직원이 동시에 같은 엑셀 파일을 열고 수정한다고 생각해 보세요. 누가 마지막으로 저장했는지에 따라 결과가 엉킬 수 있겠죠? 이것이 바로 공유 자원 접근에서 발생할 수 있는 문제입니다.
🔸 ② 비원자적 연산 – 한 번에 끝나지 않는 작업
문제는 작업이 순식간에 끝나는 하나의 동작(원자적 연산)이 아니라는 데 있습니다. 대부분의 프로그램은 데이터를 다룰 때 다음과 같은 3단계 작업을 거칩니다:
① 값을 읽고 → ② 계산하고 → ③ 다시 저장합니다.
이 세 단계가 중간에 끊기지 않고 한 번에 처리되면 문제가 없겠지만, 현실에서는 그 사이에 다른 프로세스가 끼어들 수 있습니다. 마치 엑셀에서 값을 바꾸는 중간에 다른 사람이 내용을 수정해 버리는 것처럼 말이죠.
이렇게 작업이 여러 단계로 나뉘어 있다면, 언제든지 타이밍 문제로 충돌이 발생할 수 있습니다.
🔸 ③ 실행 순서 미보장 – 누가 먼저 실행될지 모르는 상황
우리가 아무리 코드를 순서대로 짜도, 운영체제가 어떤 순서로 프로세스를 실행할지는 전혀 예측할 수 없다는 점입니다. 이것이 바로 "비결정성"의 핵심입니다.
운영체제는 동시에 실행 중인 수많은 프로세스를 스케줄링하며, 그때그때 CPU에 태우고 내리기를 반복합니다. 이 과정은 시스템의 상황(부하, 우선순위, 인터럽트 등)에 따라 계속 바뀌므로, 동일한 코드라도 매번 다른 실행 순서를 보이게 됩니다.
예를 들어, A 스레드가 먼저 실행될 것이라 생각했는데, 실제로는 B 스레드가 먼저 실행되어 결과가 바뀌는 경우가 생깁니다. 이것이 경쟁 조건이 재현되기 어렵고 디버깅이 어려운 이유이기도 합니다.
이 중 하나라도 빠지면 경쟁 조건은 발생하지 않습니다. 하지만 대부분의 실제 프로그램에서는 이 세 조건이 자주 충족되므로, 별도의 동기화 처리 없이 시스템을 설계하는 것은 위험합니다.
🔷 실무 시나리오: 온라인 쇼핑몰의 재고 문제
실제 이커머스 시스템에서 자주 발생하는 예시를 살펴보겠습니다.

✔️ 상황
특정 상품의 재고가 1개 남아있는 상태입니다.
고객 A와 고객 B가 거의 동시에 해당 상품을 구매하려고 합니다.
이때 두 명의 고객(A와 B)이 거의 동시에 해당 상품을 구매하기 위해 결제 버튼을 누릅니다.
🔸고객 A와 B는 각각 웹 또는 앱을 통해 서버로부터 재고 수량(1개)을 읽어옵니다.
🔸두 사람 모두 재고가 있다고 판단하고 주문 결제 요청을 서버에 보냅니다.
🔸서버는 동기화 없이 두 요청을 순차적으로 처리하게 되고, 다음과 같은 문제가 발생할 수 있습니다:
✔️ 가능한 문제 상황
🔸재고가 1개였지만 2개의 주문이 모두 처리되어 재고가 -1로 됨
🔸한 주문이 나중에 강제로 취소되거나, 중복 결제 오류가 발생
🔸재고가 없는데도 출고 요청이 발생해 물류팀에서 오류 처리 발생
이러한 문제는 단순한 동시 접속 문제가 아니라, 서버 내부의 동기화 실패에서 비롯됩니다. 그리고 그 근본 원인은 바로 경쟁 조건이 방치된 로직에 있습니다.
3. 임계 구역이란? – 충돌을 막기 위한 통제 구간
앞서 살펴본 경쟁 조건(Race Condition)은 대부분 특정 코드 구간에서 발생합니다. 그 구간이 바로 임계 구역(Critical Section)입니다.
멀티코어 시스템에서는 여러 프로세스나 스레드가 동시에 실행되며, 이들이 공유 자원을 동시에 접근하려 할 때 문제가 생깁니다. 예를 들어, 리스트에 데이터를 추가하거나, 계좌 잔액을 수정하는 작업은 모두 같은 자원을 다루는 행동입니다.
이처럼 충돌 가능성이 있는 중요한 코드 영역을 임계 구역이라 부릅니다.
🔷 임계 구역이란?
임계 구역(Critical Section)은 두 개 이상의 실행 흐름이 동시에 접근하면 문제가 발생할 수 있는 공유 자원을 다루는 코드 영역입니다.
이 구역에는 하나의 프로세스 또는 스레드만 들어올 수 있어야 하며, 동시에 여러 개가 진입하지 못하도록 통제해야 합니다.
🔷 예시 시나리오 : 리스트에 데이터 추가
두 스레드가 하나의 리스트에 동시에 데이터를 추가한다고 가정해 봅시다. 만약 동기화 없이 동시 접근이 이루어진다면, 다음과 같은 문제가 생길 수 있습니다:
🔸두 스레드가 동시에 같은 인덱스에 값을 삽입
🔸한쪽의 데이터가 덮어씌워지거나, 리스트 구조가 깨짐
🔸심한 경우 프로그램이 비정상 종료되거나, 데이터가 유실됨

이러한 상황을 방지하기 위해, 리스트를 수정하는 부분을 임계 구역으로 지정하고, 하나의 스레드만 진입하도록 만들어야 합니다.
🔷 임계 구역 보호를 위한 3가지 필수 조건
임계 구역은 공유 자원에 접근하는 중요한 코드 영역이기 때문에, 운영체제에서는 이를 안전하게 보호하기 위한 3가지 필수 조건을 정의하고 있습니다. 이 조건들은 동기화 기법이 갖추어야 할 최소한의 기준으로, 다음과 같습니다:
🔸 ① 상호 배제 (Mutual Exclusion) – 동시에 들어오면 안 돼요
임계 구역에서 가장 먼저 지켜야 할 원칙은 한 번에 하나만 들어올 수 있다는 것입니다.
즉, 어떤 프로세스(또는 스레드)가 이미 임계 구역에서 작업 중이라면, 다른 프로세스는 반드시 밖에서 기다려야 합니다.
이를 통해 공유 자원에 대한 충돌을 방지할 수 있고, 데이터가 꼬이거나 덮어씌워지는 현상을 막을 수 있습니다.
공용 프린터가 하나 있을 때, 두 명이 동시에 인쇄를 요청하면 문서가 엉킬 수 있습니다. 그래서 한 사람이 인쇄 작업을 마칠 때까지 다른 사람은 대기해야 합니다. 이것이 상호 배제입니다.
🔸② 진행 조건 (Progress) – 비어 있다면 바로 들어가야 해요
임계 구역에 아무도 들어가 있지 않다면, 대기 중인 프로세스들 중 누가 먼저 들어갈지 빠르게 결정되어야 합니다.
즉, 괜히 기다리게 하면 안 된다는 뜻입니다.
운영체제는 이 조건을 지키기 위해 현재 상황을 빠르게 판단하고, 진입할 수 있는 프로세스를 지체 없이 실행시켜야 합니다.
엘리베이터가 비어 있는데, 사람들은 줄을 서서도 누가 먼저 탈지 정하지 못해 우물쭈물하고 있습니다. 이건 ‘진행 조건’을 어긴 상태입니다. 비어 있으면, 바로 다음 사람이 타야 하죠.
🔸③ 한정 대기 (Bounded Waiting) – 기다리는 사람도 언젠간 들어가야 해요
임계 구역에 진입하려고 기다리고 있는 프로세스가 있다면, 언젠가는 반드시 들어갈 수 있어야 합니다.
즉, 어떤 프로세스도 영원히 기다리는 일이 없도록 만들어야 한다는 뜻입니다.
이는 시스템의 공정성(Fairness)을 보장하는 중요한 조건이며, 특정 프로세스만 계속 우선되는 구조는 바람직하지 않습니다.
식당에서 손님이 줄을 서는데, 매번 VIP 손님만 먼저 들어가고 일반 손님은 계속 밀린다면 일반 손님은 결국 포기하게 됩니다. 이런 구조는 ‘한정 대기 조건’을 지키지 않는 예입니다.
4. 동기화 3대장 – 뮤텍스, 세마포어, 모니터 쉽게 이해하기
앞서 설명한 임계 구역(Critical Section)을 안전하게 보호하려면, 실행 흐름을 조절하는 동기화 기법이 반드시 필요합니다.
이러한 기법은 마치 건물의 출입 통제 시스템처럼, 자원에 접근하는 순서를 정해 주고 충돌을 막아줍니다.
이번 장에서는 운영체제와 프로그래밍 언어에서 가장 널리 사용되는 세 가지 동기화 도구인 뮤텍스(Mutex), 세마포어(Semaphore), 모니터(Monitor)를 소개합니다.

🔷 4-1. 뮤텍스(Mutex) - 상호 배제를 위한 자물쇠
🔸 단일 자원에 대해 한 스레드만 접근하도록 보호
🔸 POSIX에서는 pthread_mutex_lock, Windows에서는 CreateMutex
🔸 사용 후 반드시 unlock 해야 함 (안 하면 데드락 발생)
뮤텍스(Mutex)는 Mutual Exclusion의 줄임말로, 임계 구역(Critical Section)에 한 번에 하나의 스레드만 진입할 수 있도록 보장하는 가장 기본적인 동기화 기법입니다.
즉, 여러 실행 흐름이 동일한 자원에 접근할 수는 있지만, 동시에 접근하는 것은 막는 장치라고 볼 수 있습니다.
✔️ 예시 시나리오: 하나뿐인 금고 열쇠

회사에 하나뿐인 금고가 있습니다. 이 금고에 여러 명의 직원이 번갈아가며 서류를 넣거나 꺼내야 하는데, 열쇠는 오직 하나뿐입니다.
누군가 금고를 사용 중이라면, 다른 사람은 열쇠가 돌아올 때까지 반드시 기다려야 합니다.
이 열쇠가 바로 뮤텍스입니다.
뮤텍스는 이러한 "순차적 접근"을 코드 수준에서 구현할 수 있게 해주는 상호 배제 도구입니다.
✔️ 작동 구조 (C - POSIX 방식)
다음은 Ubuntu 환경에서 사용하는 POSIX 스레드 기반의 뮤텍스 예시입니다
pthread_mutex_t lock; // 뮤텍스 변수 선언
// ... (뮤텍스 초기화 코드)
pthread_mutex_lock(&lock); // 임계 구역 시작 - 자물쇠 잠금
shared_data++; // 공유 자원 사용 (동시에 접근 불가)
pthread_mutex_unlock(&lock); // 임계 구역 종료 - 자물쇠 해제
이 구조는 다음과 같이 동작합니다:
▸pthread_mutex_lock(&lock) : 자원 접근을 위한 "열쇠"를 잠급니다.
▸shared_data++ : 실제 임계 구역에서 공유 자원을 조작합니다.
▸pthread_mutex_unlock(&lock) : 작업을 마치고 자원을 다른 스레드가 사용할 수 있도록 해제합니다.
✔️ 주의: 락을 해제하지 않으면 데드락(Deadlock) 발생
만약 한 스레드가 lock()은 했지만 unlock()을 하지 않고 종료된다면, 다른 스레드는 그 자원을 영원히 사용할 수 없게 됩니다.
이런 상태를 데드락(Deadlock)이라고 하며, 프로그램이 멈추는 원인이 됩니다.
따라서 뮤텍스를 사용할 때는 반드시 lock과 unlock 쌍을 정확히 맞춰야 하며, 예외 처리 코드에서도 반드시 락 해제를 고려해야 합니다.
뮤텍스는 가장 안전한 방법이지만, 사용 범위를 최소화하는 것이 중요합니다. 너무 넓게 잠그면 병목(Bottleneck)이 생기고, 성능이 저하됩니다.
✔️ Windows에서는 어떻게 사용할까?
Windows에서는 뮤텍스를 다음과 같은 API로 제공합니다
▸CreateMutex() : 뮤텍스 객체 생성
▸WaitForSingleObject() : 뮤텍스를 잠금 (자원 접근 요청)
▸ReleaseMutex() : 뮤텍스를 해제 (자원 반환)
구조는 POSIX와 다르지만, 동작 원리는 동일합니다.
즉, 다른 스레드의 접근을 잠시 차단했다가, 작업이 끝난 후 해제하는 방식입니다.
뮤텍스는 매우 간단하면서도 강력한 동기화 수단입니다. 하지만 사용 범위를 너무 넓게 설정하면 성능 저하(병목 현상)가 발생할 수 있으므로,
정말 필요한 임계 구역에만 최소한으로 적용하는 것이 실무에서의 핵심 원칙입니다.
🔷 4-2. 세마포어(Semaphore) - 더 유연한 동기화
🔸 카운팅 세마포어: 여러 자원에 동시 접근 허용 (예: 3대의 프린터)
🔸 이진 세마포어: 뮤텍스처럼 사용
세마포어(Semaphore)는 자원에 접근 가능한 "수량"을 관리하는 동기화 도구입니다.
뮤텍스가 단 하나의 자원에 대한 상호 배제를 보장하는 자물쇠라면, 세마포어는 동시에 여러 개의 접근을 허용할 수 있다는 점에서 더 유연합니다.
✔️ 예시 시나리오: 세 자리뿐인 주차장

도심의 작은 주차장에 총 3개의 주차 공간이 있다고 가정해 봅시다.
차량이 한 대 들어오면 빈자리는 하나 줄고, 나가면 다시 한 칸이 늘어납니다.
이 주차장을 운영하는 시스템이 세마포어라고 생각해 보세요:
▸ 자리가 3개 → 세마포어 값은 3
▸ 한 차량 입장 → 세마포어 감소
▸ 차량이 모두 차면 → 대기 상태로 전환
세마포어는 이렇게 자원의 사용 가능 여부를 숫자로 관리하며, 빈자리가 없으면 대기, 자리가 생기면 자동으로 다음 대기 차량을 입장시키는 역할을 합니다.
✔️ 세마포어의 두 가지 유형
| 유형 | 설명 | 주 용도 |
| 🟤 이진 세마포어 (Binary Semaphore) | 값이 0 또는 1만 가짐 | 뮤텍스처럼 단일 자원 보호 (상호 배제) |
| 🔵 카운팅 세마포어 (Counting Semaphore) | 값이 0 이상의 정수 | 여러 개의 동일한 자원 제어 (예: 프린터 3대) |
뮤텍스와 달리, 카운팅 세마포어는 동시에 여러 스레드나 프로세스의 접근을 허용할 수 있습니다.
이는 제한된 개수의 자원(예: 스레드풀, DB 커넥션, API 호출 제한 등)을 관리할 때 매우 유용합니다.
✔️ 예시 시나리오 : 생산자 - 소비자 문제(Producer - Consumer Problem)

이제 대표적인 세마포어 활용 사례인 생산자-소비자 문제를 살펴봅시다.
▸ 생산자(Producer)는 데이터를 만들어 버퍼에 넣고,
▸ 소비자(Consumer)는 버퍼에서 데이터를 꺼내어 처리합니다.
여기서 버퍼는 큐(Queue) 구조이며 크기에 제한이 있습니다. 이때 다음과 같은 문제가 생길 수 있습니다:
| 버퍼가 가득 찼을 때 | 생산자는 데이터를 더 이상 넣을 수 없음 → 대기해야 함 |
| 버퍼가 비었을 때 | 소비자는 꺼낼 데이터가 없음 → 대기해야 함 |
이 문제를 해결하려면 단순한 락만으로는 부족하며, 데이터 수량 상태에 따라 진입을 제어하는 세마포어 3개가 필요합니다.
✔️ 세마포어 구성과 역할
| 세마포어 | 초기값 | 설명 | 사용주체 |
| empty | 버퍼 크기 (예: 5) | 버퍼에 남은 빈 칸 수 | 생산자 |
| full | 0 | 버퍼에 들어 있는 데이터 수 | 소비자 |
| mutex | 1 | 임계 구역 보호 (버퍼 접근 시) | 생산자 & 소비자 |
✔️ 동작 흐름 요약
| 생산자 작업 순서 | 소비자 작업 순서 |
| wait(empty) – 버퍼에 빈 칸이 있는지 확인 | wait(full) – 버퍼에 데이터가 있는지 확인 |
| wait(mutex) – 임계 구역(버퍼) 진입 | wait(mutex) – 임계 구역(버퍼) 진입 |
| 버퍼에 데이터 삽입 | 버퍼에서 데이터 꺼내기 |
| signal(mutex) – 임계 구역에서 나가기 | signal(mutex) – 임계 구역에서 나가기 |
| signal(full) – 데이터 수 1 증가 알림 | signal(empty) – 빈 칸 하나 생김을 알림 |
여기서 mutex는 임계 구역 보호 역할이며, empty, full은 자원 상태를 세는 카운팅 세마포어입니다.
세마포어는 단순히 락을 걸고 푸는 것을 넘어, 동시 접근 수 자체를 제어하는 고급 동기화 도구입니다.
뮤텍스보다 더 유연하며, 다양한 자원 제어 시나리오에 적용 가능합니다.
▸ 생산자-소비자 패턴
▸ 서버의 동시 접속 처리
▸ 프린터, DB, API 연결 제한 등
🔷 4-3. 모니터(Monitor) – 자동으로 락과 조건변수를 관리
🔸 Java synchronized, wait(), notify()
🔸 Python에서는 threading.Condition() 사용
모니터(Monitor)는 락(lock)과 조건 변수(condition variable)를 하나로 통합한 고수준 동기화 구조입니다.
기존의 뮤텍스(Mutex)나 세마포어(Semaphore)는 개발자가 직접 락을 걸고 해제하는 로직을 작성해야 하지만,
모니터는 이러한 과정을 프로그래밍 언어가 자동으로 관리해 줍니다.
즉, 임계 구역 보호 + 조건 기반 대기/알림 처리를 함께 처리할 수 있는 강력한 구조입니다.
✔️ 예시 시나리오: 은행 창구에서 줄 서기

은행 창구에는 직원이 한 명뿐입니다. 고객들은 입금이나 출금 요청을 위해 순서를 지켜야 하며, 창구가 비어 있어야만 들어갈 수 있습니다.
▸ 고객이 창구에 진입하는 동작 = synchronized
▸ 조건이 충족되지 않으면 = wait()
▸ 업무가 끝나면 다음 고객을 깨움 = notify()
이러한 흐름이 바로 모니터의 작동 방식과 동일합니다.
✔️ Java 예시 – 모니터 구조 구현
Java에서는 synchronized 키워드를 이용해 모니터 기능을 손쉽게 구현할 수 있습니다.
아래는 특정 조건이 만족될 때만 입금을 허용하는 은행 계좌 예시입니다.
synchronized void deposit(int amount) {
while (!canDeposit()) { // 입금 가능한 상태가 아니면
wait(); // 조건 변수: 대기 상태로 전환
}
balance += amount; // 입금 수행
notify(); // 대기 중인 다른 스레드 깨우기
}
▸ synchronized: 이 메서드에 동시에 하나의 스레드만 진입 가능
▸ wait(): 조건이 만족되지 않으면 대기 상태로 전환
▸ notify(): 조건이 변경되어 대기 중인 다른 스레드를 깨움
이 구조를 사용하면 개발자가 직접 락을 명시적으로 제어하지 않아도 임계 구역 보호 + 조건 처리를 함께 구현할 수 있습니다.
✔️ Python에서는? – Condition으로 모니터 구현
Python은 threading.Condition() 객체를 이용하여 모니터와 유사한 구조를 구현할 수 있습니다.
아래는 생산자 스레드가 버퍼의 크기를 검사하고, 자리가 있을 때만 데이터를 추가하는 예시입니다.
from threading import Condition
buffer = []
cv = Condition()
def produce():
with cv: # 락 자동 획득
while len(buffer) >= 10:
cv.wait() # 조건이 충족될 때까지 대기
buffer.append(1) # 데이터 추가
cv.notify() # 대기 중인 소비자 스레드 깨우기
▸ with cv: 구문을 통해 락을 자동으로 관리하며,
▸ cv.wait()로 조건 대기,
▸ cv.notify()로 조건이 바뀐 다른 스레드를 깨울 수 있습니다.
이처럼 Python에서도 모니터의 핵심 기능인 락 + 조건 대기 + 알림을 통합하여 구현할 수 있습니다.
✔ 마무리 - 동기화는 실무에서 자주 마주치는 기본기입니다.
동기화는 개발자가 직접 마주하게 되는 실무 문제입니다.
멀티스레드 환경에서 데이터 충돌, 예상치 못한 결과, 간헐적인 오류는 대부분 동기화가 제대로 되지 않아 생깁니다.
뮤텍스, 세마포어, 모니터는 각각 상황에 따라 다르게 쓰이지만, 결국 목적은 같습니다.
공유 자원을 안전하게 다루고, 충돌을 막는 것입니다.
📌상황별 실무 정리
🔸뮤텍스: 공유 변수, 파일, DB 트랜잭션 등 단일 자원 보호에 사용
🔸세마포어: 동시에 몇 개까지만 허용해야 할 때 (예: 프린터, 커넥션 풀)
🔸모니터: 조건이 맞을 때만 진입시켜야 할 때 (예: 버퍼가 찼을 때 대기)
실제로는 이들 기법을 조합해서 쓰는 경우도 많습니다.
꼭 복잡하게 생각할 필요는 없습니다.
“공유 자원을 안전하게 다루기 위해 최소한 어떤 보호 장치가 필요할까?”
이 질문만 떠올려도, 동기화 설계는 반은 성공한 셈입니다.
※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.
'1.시스템&인프라 > 개발 입문자를 위한 운영체제' 카테고리의 다른 글
| 7편. 멀티태스킹 완전 이해 – 운영체제는 어떻게 동시에 앱을 실행할까? (0) | 2025.11.17 |
|---|---|
| 6편. 데드락(Deadlock)이란? – 프로그램이 멈추는 진짜 이유와 해결 전략 (0) | 2025.11.17 |
| 4편. 스레드란 무엇인가? – 하나의 프로세스에서 동시에 여러 일을 처리하는 원리 (0) | 2025.11.14 |
| 3편. CPU는 어떻게 일할까? – 시간 분할과 문맥 교환의 원리 (0) | 2025.11.14 |
| 2편. 프로세스란 무엇인가 – 프로그램이 살아 움직이는 방식의 이해 (0) | 2025.11.14 |