여러 작업이 서로가 쥔 자원을 기다려 아무도 진행하지 못하는 상태.
Coffman 조건 — 넷이 모두 모여야 한다
-
① 상호 배제 — 자원을 한 번에 하나만 쓸 수 있다
-
② 점유하며 대기 — 자원을 쥔 채로 다른 자원을 기다린다
-
③ 비선점 — 남이 쥔 자원을 강제로 뺏을 수 없다
-
④ 순환 대기 — A→B→C→A 처럼 대기가 원을 이룬다
-
하나만 깨도 데드락은 생기지 않는다
어떻게 다루나 — 네 가지 태도
-
예방(prevention) — 네 조건 중 하나를 원천 차단한다
- ② 깨기: 필요한 자원을 처음에 전부 잡거나, 못 잡으면 가진 것도 놓는다
- ④ 깨기: 자원에 번호를 매기고 항상 오름차순으로 잡는다 ← 가장 실용적
-
회피(avoidance) — 할당 전에 안전한지 계산한다 (은행가 알고리즘)
- 필요한 자원의 최대량을 미리 알아야 해서 현실성이 낮다
-
탐지·복구 — 주기적으로 순환을 찾고 하나를 강제 종료·롤백한다
- DB 가 이 방식을 쓴다
-
무시(타조 알고리즘) 드물면 그냥 둔다 — 범용 OS 의 실제 선택
- 예방 비용이 이득보다 크다고 판단한 것
"④를 깨는 것"이 실무의 정석이다 — 락 순서만 통일하면 순환이 구조적으로 불가능해진다.
사촌들과 구분한다
- 데드락 — 서로 기다리며 '멈춰' 있다 — CPU 를 안 쓴다
- 라이브락 — 서로 양보하며 '계속 움직인다' — 진행은 없는데 CPU 를 쓴다
- (좁은 복도에서 마주친 두 사람이 계속 같은 방향으로 비키는 상황)
- 무작위 지연(백오프)을 넣어 깬다
- 기아 — 우선순위가 낮아 영영 순서가 안 온다
- aging 으로 오래 기다린 쪽의 우선순위를 올린다
진단
# 프로세스가 D 상태(uninterruptible sleep)로 멈춰 있는지
ps -eo pid,state,wchan:32,comm | grep ' D '
# 무엇을 기다리는지
cat /proc/<pid>/stack
cat /proc/<pid>/wchan
자원은 락만이 아니다
-
커넥션 풀 — 요청당 2개가 필요한데 풀 크기가 그보다 작으면 전원이 서로를 기다린다
-
스레드 풀 — 풀의 작업이 같은 풀에 작업을 제출하고 기다리면 교착된다
-
파일 락 · 세마포어
-
"자원을 쥔 채 같은 종류의 자원을 또 요구하는 구조" 가 위험 신호다
면접 함정
- ❌ "락을 쓰면 데드락이 난다" → 네 조건이 모두 모여야 한다. 순서만 통일해도 안 난다.
- ❌ "데드락은 반드시 막아야 한다" → 범용 OS는 무시를 택했다. 비용 대비 이득의 문제다.
실무에서 만나는 형태
-
① 락 순서 불일치 — 가장 흔하다
- 계좌 A→B 이체와 B→A 이체가 동시에
-
② 자원 풀 고갈형
- 요청당 커넥션 2개가 필요한데 풀 크기가 10, 동시 요청이 10
- 전원이 첫 번째를 쥐고 두 번째를 기다린다
-
③ 스레드 풀 자기 참조
- 풀의 작업이 같은 풀에 작업을 넣고 완료를 기다린다
- 풀이 가득 차면 영원히 안 끝난다
-
② ③ 은 락이 하나도 없는데 생긴다 — "자원" 을 넓게 봐야 한다
순서를 강제하는 코드
// 항상 주소가 작은 락부터 잡는다 — 순환이 구조적으로 불가능해진다
void transfer(Account *a, Account *b) {
Account *first = (a < b) ? a : b;
Account *second = (a < b) ? b : a;
lock(first); lock(second);
/* ... */
unlock(second); unlock(first);
}
타임아웃이라는 현실적 타협
완벽한 예방이 어려우면 '영원히 기다리지 않게' 만든다
→ tryLock 에 타임아웃을 건다
→ 실패하면 가진 것을 모두 놓고 잠시 뒤 재시도한다 (백오프)
→ 데드락이 '장애' 에서 '재시도 가능한 실패' 로 바뀐다