동기화 메커니즘: 경쟁 상태를 막는 임계 구역과 락의 역할
동기화 메커니즘이 경쟁 상태를 막는 방식부터 임계 구역의 조건, 세마포어와 뮤텍스의 동작 차이, 운영체제에서의 적용 지점까지 다룹니다.
2026-08-14 · 최초 발행 2025-12-25
공유 자원에 동시에 손이 닿는 순간
동기화 메커니즘은 여러 프로세스 또는 스레드가 공유 자원에 접근할 때 그 순서를 조율하는 방법이다. 데이터 일관성과 시스템 안정성을 지키기 위해 뮤텍스, 세마포어, 선점 금지, 인터럽트 잠금, 스핀락 등을 사용한다.
핵심은 세 가지다. 한 작업이 임계 구역을 실행하는 동안 다른 작업의 진입을 막는 상호 배제, 진입 가능한 작업을 계속 대기시키지 않는 진행, 특정 작업이 무한정 기다리지 않게 하는 유한 대기다. 이런 조건은 커널, 디바이스 드라이버, 애플리케이션에서 공유 자원을 다룰 때 공통으로 요구된다.
실행 순서가 결과를 바꾸는 경쟁 상태
경쟁 상태(Race Condition)는 여러 프로세스가 공유 자원에 동시에 접근하면서, 실행 타이밍이나 순서가 결과값에 영향을 미칠 수 있는 상태다. 비원자적 연산과 컨텍스트 스위칭이 원인이 되며, 자료 일관성 훼손, 예측 불가능한 동작, 데이터 손상으로 이어질 수 있다.
운영체제에서는 커널 작업 중 인터럽트가 들어오거나 System Call 도중 컨텍스트 스위칭이 발생할 때, 또는 멀티 프로세서가 공유 메모리에 동시에 접근할 때 이런 문제가 생긴다.
인터럽트가 끼어드는 커널 작업
커널이 count를 읽고 값을 다시 저장하기 전 인터럽트가 같은 값을 변경하면, 두 증가 연산의 결과가 하나만 반영될 수 있다.
System Call 도중의 전환과 다중 CPU 접근
프로세스가 커널 모드에서 작업하는 중 타임 슬라이스가 끝나 컨텍스트 스위칭이 일어나면, 다른 프로세스가 같은 커널 데이터에 접근할 수 있다. 이 경우 커널 모드에서 선점을 금지하는 방식으로 문제를 다룬다.
여러 CPU가 공유 메모리에 동시에 쓰는 경우에는 데이터 손상이 발생할 수 있다. 이때는 락(Lock)으로 상호 배제를 보장한다.
임계 구역을 둘러싼 진입과 퇴출
임계 구역(Critical Section)은 여러 프로세스가 데이터를 공유하며 실행할 때, 공유 데이터를 액세스하는 코드 부분이다. 진입 구역에서 접근 권한을 얻고, 임계 구역을 실행한 뒤 퇴출 구역에서 권한을 놓는다. 나머지 구역은 공유 자원과 무관한 작업을 수행한다.
| 조건 | 설명 | 목적 |
|---|---|---|
| Mutual Exclusion(상호 배제) | 한 프로세스가 임계 구역 실행 중이면 다른 프로세스는 진입 불가 | 데이터 일관성 |
| Progress(진행) | 아무도 임계 구역에 없으면 진입 희망 프로세스를 진입시켜야 함 | 데드락 방지 |
| Bounded Waiting(유한 대기) | 프로세스가 임계 구역 진입을 무한정 대기해서는 안 됨 | Starvation 방지 |
피터슨 알고리즘(Peterson's Algorithm)은 소프트웨어로 상호 배제를 구현하는 고전적 알고리즘이다. 각 프로세스는 진입 의사를 표시하고 상대방에게 차례를 양보한 뒤, 상대방의 상태와 차례를 확인하며 대기한다.
// 공유 변수
int turn; // 누구 차례인지
bool flag[2]; // 진입 의사 표시
// 프로세스 i
flag[i] = true; // 진입 의사 표시
turn = j; // 상대방에게 양보
while (flag[j] && turn == j); // 대기
// Critical Section
flag[i] = false; // 퇴출
세마포어가 대기열을 다루는 방식
세마포어(Semaphore)는 자원 개수를 나타내는 정수 변수이며, P()와 V() 원자적 연산으로 동시 접근을 제어하는 추상적 동기화 메커니즘이다.
임계 구역이 짧을 때는 자원을 얻을 수 있을 때까지 반복 확인하는 Busy-Wait 방식을 사용할 수 있다. 구현은 단순하지만 대기 중에도 CPU를 사용한다.
P(s) {
while (s <= 0) do wait; // 대기
s--; // 자원 획득
}
V(s) {
s++; // 자원 반납
}
Block-Wakeup 방식은 대기 중인 프로세스를 큐에 넣어 block하고, 자원이 반납되면 큐에서 꺼내 wakeup한다. 임계 구역이 긴 경우에 적합하지만 컨텍스트 스위칭 오버헤드가 발생한다.
typedef struct {
int value; // 세마포어 값
struct process *Q; // 대기 큐
} semaphore;
P(S) {
S.value--;
if (S.value < 0) {
add this process to S.Q;
block();
}
}
V(S) {
S.value++;
if (S.value <= 0) {
remove a process P from S.Q;
wakeup(P);
}
}
| 항목 | Busy-Wait | Block-Wakeup |
|---|---|---|
| CPU 사용 | 대기 중에도 CPU 사용 | 대기 중 CPU 양보 |
| 컨텍스트 스위칭 | 없음 | 발생 (오버헤드) |
| 적합한 경우 | 임계 구역 짧음 | 임계 구역 김 |
| 에너지 효율 | 낮음 | 높음 |
뮤텍스는 소유권이 있는 잠금이다
뮤텍스(Mutex)는 Binary Semaphore와 동일한 개념으로, 하나의 작업만 자원에 접근하도록 하는 상호 배제 메커니즘이다. 값은 0(잠금) 또는 1(해제)이며, Lock을 획득한 스레드만 Unlock할 수 있다. 같은 스레드의 중복 Lock을 허용하는 Recursive Mutex와 우선순위 역전 문제를 해결하는 우선순위 상속도 관련 특성이다.
mutex_lock(&lock); // 임계 구역 진입
// Critical Section
mutex_unlock(&lock); // 임계 구역 퇴출
뮤텍스는 한 작업의 상호 배제에 맞고, 세마포어는 자원 관리와 순서 제어에 사용된다.
| 항목 | Mutex | Semaphore |
|---|---|---|
| 동기화 대상 | 1개 (Binary) | 1개 이상 (Counting) |
| 소유권 | 있음 (Lock한 스레드만 Unlock) | 없음 (아무나 V() 가능) |
| 용도 | 상호 배제 | 자원 관리, 순서 제어 |
| 재귀 | 가능 (Recursive Mutex) | 불가능 |
공유 카운터처럼 읽기와 쓰기가 이어지는 코드에서는 뮤텍스로 임계 구역을 감싼다.
int counter = 0;
pthread_mutex_t lock;
void* increment(void* arg) {
pthread_mutex_lock(&lock);
counter++; // Critical Section
pthread_mutex_unlock(&lock);
return NULL;
}
보호하지 않으면 경쟁 상태로 counter 값이 일치하지 않을 수 있다. 뮤텍스를 사용하면 상호 배제로 일관성을 보장한다.