Contents
1. 동시성 제어 개념
2. 동기화 기법
1. 동시성 제어 개념
1.1 기본 개념
경쟁 조건(race condition):
- 여러 프로세스·스레드가 같은 데이터를 동시에 수정할 때 타이밍이나 실행 순서에 따라 결과가 달라지는 상황
동기화(synchronizaton):
- 여러 프로세스·스레드가 실행되어도 데이터 일관성을 유지하도록 제어하는 것
- 공유 자원에 대한 접근 순서를 제어하여 데이터 일관성을 유지하는 것
임계 영역(critical section):
- 공유 데이터를 접근하는 코드 영역
- 일관성을 보장하기 위해 동시에 하나의 프로세스·스레드만 진입할 수 있는 영역
1.2 임계 영역 문제(Critical Section Problem)
do {
// enter section: 임계 영역에 진입할 수 있는지 확인
// critical section
// exit section: 다른 프로세스·스레드가 임계 영역에 진입할 수 있도록 처리
} while(true)해결 조건:
-
상호 배제(mutual exclusion)
- 한 프로세스·스레드만 임계 구역에 들어갈 수 있다.
- 임계 영역 보호
-
진행(progress)
- 임계 영역에 아무도 없으면 반드시 누군가(프로세스·스레드)는 들어가야 함
- 불필요한 대기 방지
-
한정 대기(bounded waiting)
- 특정 프로세스·스레드가 무한정 기다리면 안됨
- 기아(starvation) 방지(대기 횟수에 상한이 존재해야 함)
2. 동기화 기법
[하드웨어]
↓
atomic 연산
↓
lock (spinlock / mutex)
↓
semaphore
↓
monitor / synchronized
동기화는 atomic 연산을 기반으로 시작하여, lock, semaphore, monitor로 추상화 수준이 점점 높아진다.
2.1 Atomic 연산
락을 구현하기 위한 기본 단위
atomic 연산:
- 실행 중간에 간섭받거나 중단되지 않는다.
- 같은 메모리 영역에 대해 동시에 실행되지 않는다.
2.2 Lock 기반 동기화
do {
// acquire lock: 락을 획득하기 위해 경합, 획득 실패 시 대기(spin or sleep)
// critical section: 락을 획득한 프로세스·스레드만이 임계영역에 들어가 실행
// release lock: 락을 반환
} while(true)상호 배제(mutual exclusion)를 보장하기 위한 방법
2.2.1 Lock 구현 방식

-
스핀락(Spinlock)
- lock을 얻을 때까지 반복해서 확인(busy wait)
- CPU 계속 사용(낭비)
-
뮤텍스(Mutex)
- Lock을 얻지 못하면 sleep 상태로 전환
- CPU 낭비 없음
- context switching 비용 발생
2.2.2 선택 기준
멀티 코어 && 임계 영역이 매우 짧음 → Spinlock
- 다른 코어에서 lock이 곧 풀리므로 기다리는게 더 빠름
- context switching 비용 회피
싱글 코어 || 임계 영역이 김 → Mutex
- lock을 획득하지 못한 프로세스·스레드가 상태를 계속 확인해야 하므로 CPU 낭비(비효율)
- CPU를 점유하고 있으면 동안 다른 프로세스·스레드가 실행되지 못할 수 있음
- sleep을 통해 CPU를 양보하는 것이 더 효율적
2.3 세마포어(Semaphore)
여러 개의 프로세스·스레드 접근을 제어하는 동기화 도구
Lock을 이용해 구현되며, 동시에 접근 가능한 개수를 제어할 수 있음
- Semaphore는 mutual exclusion 뿐만 아니라 resource counting과 thread coordination을 지원한다.
2.3.1 특징
- 내부 counter 사용
- 자원의 개수를 제어하거나, 스레드 간 실행 순서를 동기화할 때 사용
- wait(P): 자원 획득
- signal(V): 자원 반환
- 소유권이 없음
- 자원을 획득한 스레드가 아니어도 다른 스레드가 반환(signal)할 수 있음
2.3.2 종류
Binary Semaphore:
- 0 또는 1
- Mutex처럼 사용 가능
- 하지만 Mutex와 다르게 소유권이 없어 다른 스레드가 signal할 수 있음
Counting Semaphore:
- 동시에 N개까지 허용
2.3.3 Mutex와의 차이
Mutex:
- 하나의 스레드만 접근 가능
- 소유권 존재
- 내부적으로 Lock을 획득한 스레드(owner)를 기록함
- Lock을 건 스레드만 해제 가능
- 일부 시스템에서 priority inversion 문제를 해결하기 위해 priority inheritance 기법이 적용될 수 있다.
Semaphore:
- 여러 개의 스레드 접근 가능(또는 1개)
- 소유권 없음
- 내부적으로 count(자원 개수)만 관리
- 어떤 스레드가 자원을 획득했는지 기록하지 않음
- 다른 스레드가 signal로 자원 반환 가능
- 자원의 개수 및 스레드 간 협업/동기화에 적합
상호 배제만 필요하면 Mutex를, 작업 간의 실행 순서 동기화가 필요하면 Semaphore를 권장한다.
2.4 모니터(Monitor)

Lock과 조건 대기(wait/signal)를 하나로 묶어, 개발자가 직접 Lock/Unlcok을 관리하지 않아도 동기화가 이루어지도록 만든 구조
2.4.1 특징
- mutual exclusion 자동 보장
- 조건에 따라 스레드를 대기(wait) / 깨움(signal)
- 동기화 로직을 하나의 구조로 묶어 안전하게 관리
2.4.2 구성 요소
-
Mutex(상호 배제)
- 한번에 하나의 스레드만 monitor 내부에 진입 가능
- 이미 다른 스레드가 실행 중이면 Entry Queue에서 대기
-
Condition Variable(조건 대기)
- 특정 조건이 만족될 때까지 스레드를 대기시킴
- 조건이 만족되지 않으면 → Waiting Queue에서 대기
2.4.3 큐 구조
Entry Queue:
- monitor에 진입하기 위해 lock을 기다리는 큐
Waiting Queue:
- 조건이 충족되기를 기다리는 큐(wait 상태)
2.4.4 주요 연산
wait():
- 조건이 만족되지 않으면 호출
- 현재 스레드를 waiting queue로 이동
- lock을 반납하고 대기 상태로 전환
- 다른 스레드가 monitor에 진입할 수 있게 함
signal():
- 조건이 만족되었을 때 호출
- waiting queue에서 하나의 스레드를 깨움
- 깨워진 스레드는 다시 실행 시도
broadcast():
- waiting queue의 모든 스레드를 깨움
2.4.5 Monitor 동작 흐름
acquire(mutexLock); // lock 획득 (실패 시 Entry Queue에서 대기)
while (!p) { // 조건 확인
wait(mutexLock, conditionVariable); // 조건 불충족 → Waiting Queue로 이동
}
signal(conditionVariable2); // 다른 조건 대기 스레드 깨움
release(mutexLock); // lock 반환-
Lock 획득
acquire(mutexLock)- 뮤텍스 락을 취득하지 못한 스레드는 Entry Queue에서 대기한다.
-
조건 확인 및 대기
while(!p)- 조건이 만족되지 않으면
wait()호출
- 조건이 만족되지 않으면
wait(mutexLock, conditionVariable)- 현재 스레드를 conditionVariable의 Waiting Queue로 이동
- mutexLock을 자동으로 반납
- 다른 스레드가 monitor에 진입 가능
-
스레드 깨우기
signal(conditionVariable2)- 특정 conditionVariable에 연결된 Waiting Queue에서 하나의 스레드를 깨움
- 어떤 조건 큐를 깨울지는 conditionVariable로 구분
- conditionVariable2는 이전과 같을 필요 없음
- broadcast() 사용 시 → 해당 조건 큐의 모든 스레드 깨움
-
Lock 반환
release(mutexLock)- monitor 실행 종료
- Entry Queue에서 대기 중인 스레드 중 하나 실행
2.4.6 bounded producer / consumer problem (Monitor, Signal and Continue)

Consumer가 먼저 실행되어 조건 불충족으로 대기하고, Producer가 데이터 생성 후 signal을 통해 깨우는 흐름
Monitor에서의 대기 구조:
- Entry Queue
- Mutex lock을 얻지 못한 스레드가 대기하는 큐
- Waiting Queue(condition variable)
- 조건이 만족되지 않아
wait()로 대기하는 큐
- 조건이 만족되지 않아
동작 흐름:
-
C1 시작
- C1이 lock 획득
- buffer 비어있음 → 소비 불가
wait(emptyCV)호출- Waiting Queue(emptyCV)로 이동
- lock 반납
-
P1 실행
- Entry Queue에 있던 P1 깨어남
- P1 lock 획득
- buffer에 T1 삽입
signal(emptyCV)→ C1 깨움- C1은 깨어나지만 Entry Queue에서 대기(Signal and Continue)
- P1은 계속 실행 중(lock 유지)
-
P2, C2 시작
- lock 획득 실패로 Entry Queue에서 대기
-
P1 lock 반환
- 대기하고 있던 C1 깨어남
-
C1 실행
- buffer에서 T1 꺼냄
signal(fullCV)실행, Waiting Queue(fullCV) 확인- lock 반환
-
이후 순차 실행
- Entry Queue에 있던 스레드 실행
- P2 → C2(FIFO 가정)
2.4.7 Signal 방식
Signal and Continue:
- signal한 스레드가 계속 실행
- 깨어난 스레드는 Entry Queue에서 대기
- 조건 다시 확인 필요(while)
- Java, C#, …
- 구현 단순 / while이 반드시 필요함 / 불필요한 context switching 줄임
Signal and Wait:
- signal한 스레드가 대기
- 깨어난 스레드가 즉시 실행
- 조건이 보장됨
- 이론
2.4.8 자바에서의 Monitor
자바에서 모든 객체는 내부적으로 모니터(monitor)를 가지며,
synchronized와wait / notify를 통해 동기화를 제공한다.
역할:
상호 배제(Mutual Exclusion):
synchronized키워드를 통해 한 번에 하나의 스레드만 실행
조건 동기화(Condition Synchronization):
wait(),notify(),notifyAll()을 통해 스레드 간 협업
주요 메서드:
wait()- 현재 스레드를 Waiting 상태로 전환
- lock을 반환
- 다른 스레드고
synchronized영역 진입 가능
- 다른 스레드고
notify()- Waiting 상태의 스레드 하나 깨움
signal()과 동일
- Waiting 상태의 스레드 하나 깨움
notifyAll()- Waiting 상태의 스레드 모두 깨움
boradcast()와 동일
- Waiting 상태의 스레드 모두 깨움
synchronized:
- 메서드 단위
synchronized void method() {
// critical section
}- 블럭 단위
synchronized(obj) {
// critical section
}
synchronized(this) // 현재 객체 lock
synchronized(lock) // 특정 lock 객체 사용- 어떤 객체를 기준으로 lock을 걸지 직접 선택할 수 있음
Java의 monitor는 condition variable이 하나만 존재한다.
- 따라서 여러 조건을 동시에 관리하려면 추가적인 객체나 로직을 직접 구현해야 함.
2.4.9 Semaphore 와의 차이
Semaphore는 자원의 개수와 실행 순서를 개발자가 직접 관리해야 하고, Monitor는 조건만 표현하면 lock과 대기 상태를 자동으로 처리한다.
Semaphore:
- count(자원 개수) 기반 제어
wait()/signal()을 직접 관리해야 함- 동기화 로직을 개발자가 직접 구현
Monitor:
- 조건(condition) 기반 제어
synchronized+wait / notify로 자동관리- lock과 대기 상태를 구조적으로 처리
| 구분 | Semaphore | Monitor |
|---|---|---|
| 제어 기준 | count (숫자) | 조건 (상태) |
| 동기화 방식 | 수동 제어 | 구조적 자동 제어 |
| 표현 방식 | 자원 개수 | 상태 조건 |
| 구현 난이도 | 높음 | 낮음 |
| 실수 가능성 | 높음 | 낮음 |