I'm FanJae.
[Multi Thread] 5. 교착 상태(Deadlock)와 해결 방법 (잠금 순서, 재귀 뮤텍스) 본문
[Multi Thread] 5. 교착 상태(Deadlock)와 해결 방법 (잠금 순서, 재귀 뮤텍스)
FanJae 2026. 5. 3. 23:14[멀티 스레드 시리즈 글]
[Multi Thread] 1. 멀티 스레드를 이해하기 위한 기초 (프로그램, 프로세스, 스레드)
[Multi Thread] 2. 호출 스택과 실행 흐름
[Multi Thread] 3. 스레드는 어떻게 동작하는가 (컨텍스트 스위칭과 데이터 레이스)
1. 교착 상태
- 멀티스레드 프로그래밍에서 교착 상태란 여러 스레드가 서로가 점유한 자원을 기다리며, 더 이상 진행하지 못하는 상태를 의미한다.
- 이때, 교착 상태는 다음 4가지 조건이 동시에 만족될 때 발생한다.
1) 교착 상태가 발생하는 조건
① 상호 배제
- 특정 자원이 하나의 스레드에서만 점유 가능한 자원일 때, 교착 상태가 발생할 수 있는 조건 중 하나가 만족된다.
② 점유와 대기
스레드가 이미 어떤 자원을 점유한 상태에서 다른 자원을 추가로 기다린다.
③ 비선점
다른 스레드가 점유한 자원을 강제로 빼앗을 수 없다.
④ 순환 대기
- 스레드들이 서로가 가진 자원을 원형 구조로 기다린다.


- 위와 같은 예시가 있다고 하자. 이때, 두 스레드를 번갈아 실행한다고 가정한다.
- 이 상태에서 왼쪽 스레드의 Lock(a)가 실행된다. 이후, 오른쪽 스레드에 있는 lock(b)가 실행된다.
- a는 왼쪽 스레드가 잠구고, b는 오른쪽 스레드가 잠군다.
- 이 상태에서 왼쪽 스레드의 lock(b)를 실행하지만, 이미 오른쪽 스레드로 잠근 상태이다.
- 따라서 왼쪽 스레드는 오른쪽 스레드가 b를 풀어줄 때 까지 기다려야 한다.
- 이는 오른쪽 스레드도 동일하다. a를 왼쪽 스레드가 잠근 상태이므로, 왼쪽 스레드가 풀어줄 때 까지 기다려야 한다.
2) 게임 서버에서 교착 상태가 되면 발생하면 현상
- CPU 사용량이 현저히 낮거나 0%가 나온다. 이는 동시 접속자 수와 상관이 없다.
- 클라이언트가 서버 이용이 불가능하다.
- 즉, 서버가 죽은 상태가 되는 것이다.
3) 교착 상태 확인 방법
- 윈도우에서 제공하는 임계 영역 기능인 CRITICAL_SECTION 내용을 디버거로 확인하여 교착 상태의 시작점을 체크할 수 있다.
- Debug > Windows > Threads 메뉴에서 이를 확인할 수 있다.

- 위와 같이 Thread의 교착 상태를 확인할 수 있다.
2. 잠금 순서 규칙
여러 뮤텍스를 사용할 때 교착 상태를 예방하려면 각 뮤텍스의 잠금 순서를 정리해둬야한다.
이후, 새 코드를 작성할 때 그 순서를 어기지 않는지 확인한다.
예를 들어 잠금 순서를 A → B → C로 정했다고 가정한다.
그러면, 여러 뮤텍스를 함께 사용할 때 항상 이 순서대로 잠금을 획득해야 한다.
lock(A)
lock(B)
lock(C)
unlock(C)
unlock(B)
unlock(A)
- 해제는 보통 획득한 순서의 반대로 수행한다.
- 위 예시에서는 A → B → C 순서로 잠금을 획득했기 때문에, C → B → A 순서로 해제한다.
- 아래와 같은 순서도 안전하다.
lock(A)
lock(B)
unlock(B)
unlock(A)
- 왜냐하면, A→B→C라는 전체 잠금 순서 중 A → B 순서를 어기지 않았기 때문이다.
- A→C 순서로 가는 것도 안전하다.
lock(A)
lock(C)
unlock(C)
unlock(A)
- A → B → C라는 전체 잠금 순서에서 B를 사용하지 않았다.
- 하지만, A가 C보다 먼저 잠겨야 한다는 규칙은 지켰기 때문이다.
※ 즉, 잠금 순서 규칙의 목적은 교착 상태의 4가지 조건 중 순환 대기를 깨는 것에 있다.
3. 재귀 뮤텍스(Recursive Mutex)
- 잠금 순서 규칙은 여러 뮤텍스를 함께 사용할 때의 교착 상태를 예방하기 위한 방법이다.
- 그런데 교착 상태와 비슷하게 보이지만, 조금 다른 경우도 있다.
lock(A)
lock(A)
unlock(A)
unlock(A)
- 일반 뮤텍스라면 두 번째 lock(A)에서 자기 자신이 이미 점유한 A를 다시 기다리게 될 수 있다.
- 이 경우 스레드가 자기 자신 때문에 멈추는 문제가 발생할 수 있다.
- 재귀 뮤텍스는 같은 스레드가 이미 획득한 뮤텍스를 다시 잠그는 것을 허용한다.
lock(A) // count = 1
lock(A) // count = 2
unlock(A) // count = 1
unlock(A) // count = 0, 실제 해제
- 즉, 재귀 뮤텍스는 내부적으로 같은 스레드가 몇 번 잠갔는지를 카운트한다.
- 그래서 잠근 횟수만큼 unlock해야 실제로 뮤텍스가 해제된다.
- 다만 재귀 뮤텍스는 같은 스레드가 같은 뮤텍스를 다시 잠그는 상황을 허용할 뿐이다.
- 여러 스레드가 서로 다른 뮤텍스를 다른 순서로 잠그는 문제를 해결하지는 못한다.
Thread 1: lock(A) → lock(B)
Thread 2: lock(B) → lock(A)
- 위와 같은 상황은 재귀 뮤텍스를 사용하더라도 교착 상태가 발생할 수 있다.
- 따라서 재귀 뮤텍스를 사용하더라도 잠금 순서 규칙은 여전히 필요하다.
- 재귀 뮤텍스는 편리하지만, 같은 뮤텍스를 여러 번 잠그는 구조가 생겼다는 뜻이기도 하다.
- 따라서 설계가 복잡해졌다는 신호일 수 있으므로, 일반 뮤텍스로 구조를 단순하게 만들 수 있는지 먼저 검토하는 것이 좋다. 즉, 설계를 잘 신경써서 해야 한다.
'System Programming' 카테고리의 다른 글
| [Multi Thread] 4. 경쟁 상태와 동기화 (원자성, 일관성, 뮤텍스) (0) | 2026.05.01 |
|---|---|
| [Multi Thread] 3. 스레드는 어떻게 동작하는가 (컨텍스트 스위칭과 데이터 레이스) (0) | 2026.04.28 |
| [Multi Thread] 2. 호출 스택과 실행 흐름 (0) | 2026.04.26 |
| [Multi Thread] 1. 멀티 스레드를 이해하기 위한 기초 (프로그램, 프로세스, 스레드) (0) | 2026.04.25 |
| [System Programming] 컴퓨터 구조의 접근방법 (4) | 2024.09.20 |