프로세스 동기화 & 데드락
2026. 7. 21. 10:30
- -

이번엔 동시성 문제 (Race Condition)이 생기는 이유부터 데드락, 그리고 데드락 해결까지 다룰 예정이다.
1. Race Condition (동시성 문제)
Race Condition이란, 두 개의 연산이 동시에 공유 자원에 접근할 때 결과가 일관성을 보장받지 못하는 상태를 의미한다.
Race의 뜻 그대로, 여러 프로세스/스레드가 공유 자원에 먼저 도달해서 작업을 끝내려고 "경쟁"하는 상황을 비유한 이름이다.
동시성 문제는 다음 두 조건이 동시에 충족될 때 발생한다.
- 공유 자원 사용: 여러 스레드/프로세스가 동일한 자원(변수, 메모리 등)을 동시에 사용함 (예: 전역 변수, 파일, DB)
- 동시성: 두 스레드/프로세스가 동시에 실행되며, 실행 순서를 운영체제가 제어하지 못함 (예: 문맥 전환으로 스레드 작업이 중단됐다가 다시 실행됨)
Race Condition 예시
공유된 은행 계좌(초기 잔액 100원)에 스레드 A는 50원 입금, 스레드 B는 30원 출금을 동시에 수행하는 상황.
읽기 → 계산 → 저장 3단계로 나눠서 보면:
- 읽기: A는 잔액 100을 읽음 / B도 잔액 100을 읽음
- 계산: A는 100+50=150 계산 / B는 100-30=70 계산
- 저장: A가 150을 저장 → B가 70을 저장 (덮어씀)
최종 잔액은 70원. 원래대로면 100+50-30=120원이어야 하는데, A의 결과가 B의 결과에 덮어씌워지면서 틀린 값이 나온다. "동시에 값을 읽고 각자 계산해서 나중에 쓰는" 구조 자체가 문제의 원인이다.
OS 내부에서 Race Condition이 생기는 경우 및 해결 방법
| 문제 상황 | 해결 |
| 커널 모드에서 데이터를 로드해 수행하던 중 인터럽트가 발생해서 같은 데이터에 접근하는 경우 | 중요한 데이터에 접근 중일 때는 인터럽트를 바로 처리하지 않고 해당 작업 이후로 지연 처리 |
| 프로세스가 커널 모드 수행 중인데 문맥 교환이 발생하는 경우 | 커널 모드 수행 중엔 CPU를 빼앗기지 않고(비선점), 사용자 모드로 돌아갈 때 CPU 반납 |
| 다중 CPU 환경에서 여러 CPU가 같은 데이터에 접근하는 경우 | 공유 데이터에 락(lock/unlock)을 걸어서 다른 접근을 차단 (모니터 락, concurrent lock 등) |
2. 임계 영역 (Critical Section)
임계 영역이란 두 개 이상의 프로세스나 스레드가 동시에 접근하면 안 되는, 공유 자원을 사용하는 코드 영역을 의미한다.
데이터 일관성을 지키려면 한 번에 하나의 프로세스/스레드만 이 영역을 실행하도록 제어해야 한다.
임계 영역 문제(Critical Section Problem)
여러 프로세스가 동시에 임계 영역에 접근하면 Race Condition이 발생할 수 있다.
그래서 운영체제는 다음 세 가지 조건을 만족시켜서 이 문제를 해결한다.
- 상호 배제 (Mutual Exclusion): 한 번에 하나의 프로세스만 크리티컬 섹션에 진입할 수 있어야 함
- 진행 조건 (Progress): 크리티컬 섹션에 아무도 없으면 누군가는 들어갈 수 있어야 함
- 유한 대기 (Bounded Waiting): 대기 중인 프로세스가 일정 시간 내에는 반드시 크리티컬 섹션에 진입할 수 있어야 함
각 조건이 깨졌을 때 어떤 문제가 생기는지 보면 이해가 좀 더 쉽다.
- 상호 배제 불충족: 위 은행 계좌 예시처럼 여러 프로세스가 동시에 접근 → 데이터 일관성 붕괴
- 진행 조건 불충족: 아무도 크리티컬 섹션에 없는데도 아무도 못 들어감 → 자원 낭비
- 유한 대기 불충족: 우선순위 기반으로만 접근 순서를 정하면, 높은 우선순위 프로세스가 계속 들어와서 낮은 우선순위 프로세스는 계속 기다리기만 함 (CPU 스케줄링에서 본 starvation과 같은 종류의 문제)
3. 해결 방법 : 세마포어(Semaphore)
세마포어는 운영체제에서 여러 프로세스/스레드가 공유 자원에 접근하는 걸 동기화하기 위해 쓰는 기술이다.
이는 정수 값으로 자원 접근 가능 여부를 제어한다.
- 값이 0보다 크면 자원을 사용할 수 있고, 0이면 자원이 모두 사용 중
- P(S) 연산: 자원 획득 연산. 세마포어 값을 1 감소시킴 (값이 0이면 대기)
- V(S) 연산: 자원 반납 연산. 세마포어 값을 1 증가시킴
예시로 보는 동작
공유 자원을 쓰는 프로세스 A, B, C가 있고 자원이 2개만 있다고 가정.
- 초기 세마포어 값 = 2
- A가 P(S) 호출 → 값 2→1, 자원 사용
- B가 P(S) 호출 → 값 1→0, 자원 사용
- C가 P(S) 호출 → 값이 0이라 대기
- A가 작업 완료 후 V(S) 호출 → 값 0→1, C가 자원 사용 가능
세마포어의 종류
- 카운팅 세마포어(Counting Semaphore): 0 이상의 정수 값을 가지며 동시 접근 가능한 자원의 개수를 나타냄 (예: 프린터 3대 → 최대 3개 프로세스가 동시 사용 가능)
- 이진 세마포어(Binary Semaphore) = 뮤텍스(Mutex): 값이 0 또는 1만 가능. 한 번에 하나의 프로세스만 접근 가능
예시 수도 코드
semaphore S = 1; // 세마포어 초기화
void process() {
while (true) {
P(S); // 세마포어 감소 (Critical Section 진입 요청)
// Critical Section: 공유 자원 처리 로직
V(S); // 세마포어 증가 (Critical Section에서 나옴)
// Remainder Section: 공유 자원과 무관한 나머지 작업
}
}
대기 상태 처리 방식 두 가지
Busy-Wait
- 자원을 쓸 수 있을 때까지 세마포어 값을 계속 확인하며 대기 (while (S <= 0) {})
- CPU를 계속 쓰면서 루프를 반복하니까 자원 낭비가 있음
Block/Wakeup
- 자원이 사용 중이면 프로세스를 Blocked 상태로 전환하고, 자원이 가용해지면 깨워서(Wakeup) 실행 재개
- P(S) 호출 시 값이 0 미만이면 프로세스가 block() 호출 → 운영체제가 이 프로세스를 세마포어의 Wait Queue에 넣고 CPU 제어권을 빼앗음
- V(S) 호출 시 Wait Queue에서 대기 중인 다른 프로세스를 깨워서 실행 재개
- CPU를 낭비하지 않고 효율적으로 대기함
다만 무조건 Block/Wakeup이 좋은 건 아니다. Critical Section 실행 시간이 짧으면 Block/Wakeup의 오버헤드(큐에 넣고 빼고 깨우는 과정)가 오히려 더 커질 수 있어서, 이런 경우엔 Busy-Wait이 더 효율적일 수도 있다.
4. 데드락(Deadlock, 교착 상태)
두 개 이상의 프로세스가 서로가 소유한 자원을 요청하며 대기 상태에 빠져, 더 이상 실행을 진행할 수 없는 상황이 데드락이다.
이는 대부분 공유 자원이나 동기화 기법을 잘못 설계했을 때 발생한다.
데드락 발생 조건 (4가지가 동시에 충족될 때 발생)
- 상호 배제(Mutual Exclusion): 자원은 한 번에 하나의 프로세스만 사용 가능 (예: 프린터, 파일 접근)
- 점유 대기(Hold and Wait): 프로세스가 이미 할당된 자원을 점유한 채로, 추가로 다른 자원을 요청하며 대기함
- 비선점(No Preemption): 이미 할당된 자원은 해당 프로세스가 끝낼 때까지 강제로 뺏을 수 없음
- 순환 대기(Circular Wait): 프로세스들이 순환 형태로 자원을 요청하며 대기 (예: A가 B의 자원을 요청, B가 A의 자원을 요청)
대표적인 예시: P1이 자원 A를 가진 채 자원 B를 요구하고, P2가 자원 B를 가진 채 자원 A를 요구하면 P1, P2 둘 다 더 이상 진행이 불가능해진다.
참고로 자원 할당 그래프에 사이클이 있다고 무조건 데드락은 아니다. 예를 들어 위 예시에서 공유 자원 A가 2개 있다면, 사이클이 있어도 P2가 작업을 진행할 수 있어서 데드락이 발생하지 않는다. 즉 사이클은 데드락의 필요조건이지 충분조건은 아니다.
데드락 해결 방법 3가지 (순서대로 강도가 높아짐)
예방 (Prevention)
예방(Prevention)은 데드락이 발생하려면 위 4가지 조건이 모두 필요하니까, 그중 하나 이상을 애초에 충족 못 하게 설계해서 원천적으로 방지한다.
| 방지 대상 | 방법 | 다단점 |
| 상호 배제 방지 | 공유 자원을 여러 프로세스가 동시에 쓸 수 있게 설계 (예: 읽기 전용 데이터는 동시 접근 허용) | 쓰기 작업은 여전히 상호 배제 필요 |
| 점유 대기 방지 | 필요한 자원을 한꺼번에 요청하도록 강제 | 자원 낭비 가능 (필요 없는 자원도 미리 점유) |
| 비선점 방지 | 자원이 점유 중이면 강제로 빼앗아 다른 프로세스가 쓸 수 있게 함 | 데이터 일관성 문제, 오버헤드 발생 가능 |
| 순환 대기 방지 | 자원에 번호를 매기고 항상 번호가 증가하는 순서로만 요청하게 함 | 요청 순서를 엄격히 지켜야 해서 유연성이 떨어짐 |
회피 (Avoidance)
회피(Avoidance)는 시스템의 현재 상태를 평가해서, 데드락이 발생하지 않을 경우에만 자원을 할당한다. 프로세스의 최대 자원 요구량을 미리 알아야 한다.
- 동작: 프로세스가 자원을 요청하면, 할당했다고 가정했을 때의 시스템 상태를 시뮬레이션 → 안전 상태(모든 프로세스가 실행을 완료할 수 있는 상태)면 할당, 아니면 요청 거절
- 장점: 데드락 발생 가능성을 최소화
- 단점: 자원의 최대 요구량을 사전에 알아야 하고, 계산 비용이 증가함
탐지 및 회복 (Detection & Recovery)
- 탐지: 데드락이 발생했는지 주기적으로 검사. 자원 할당 그래프에서 사이클 존재 여부를 확인
- 회복: 데드락을 탐지한 후 문제를 해결하는 조치
- 프로세스 종료: 데드락을 유발하는 프로세스를 하나 이상 종료 (전체 종료 또는 우선순위 낮은 것부터 하나씩)
- 자원 선점: 특정 프로세스에서 자원을 강제로 회수해서 다른 프로세스가 먼저 끝내도록 하고, 선점당한 프로세스는 나중에 다시 실행. 단점은 데이터 일관성 문제가 생길 수 있음
여담 : 실무에서 택하는 방식
예방·회피·탐지 및 회복 모두 나름의 비용(복잡한 알고리즘, 유연성 저하, 계산 비용)이 있고, 실제로 데드락 발생 확률 자체가 낮은 경우가 많아서 아예 무시하는 전략을 쓰기도 한다. 이 경우 데드락이 실제로 생기면 사용자가 수동으로 문제를 해결한다 (프로세스 종료 및 재시작).
정리
여러 프로세스가 공유 자원을 동시에 건드리면 Race Condition이 생기고, 이를 막기 위한 코드 영역이 크리티컬 섹션이다. 세마포어(P/V 연산)로 한 번에 하나씩만 접근하게 만들 수 있지만, 자원을 점유한 채 서로를 기다리게 설계하면 데드락에 빠질 수 있다. 데드락은 예방·회피·탐지 및 회복 순으로 강도 높은 해결책이 있지만, 실무에서는 발생 확률과 비용을 따져 아예 무시하는 전략도 쓴다.
'🔬 CS > OS' 카테고리의 다른 글
| CPU 스케줄링 (0) | 2026.07.20 |
|---|---|
| 프로세스 3 - 스레드 개념과 활용, 멀티 프로세스 (0) | 2026.07.19 |
| 프로세스 2 : PCB (프로세스 제어 블록) (0) | 2026.07.19 |
| 프로세스 1 : 문맥과 상태 & 큐와 스케줄러 (0) | 2026.07.18 |
| 하드웨어 수행 과정 (0) | 2026.07.18 |
소중한 공감 감사합니다