Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

I'm FanJae.

[Multi Thread] 5. 교착 상태(Deadlock)와 해결 방법 (잠금 순서, 재귀 뮤텍스) 본문

System Programming

[Multi Thread] 5. 교착 상태(Deadlock)와 해결 방법 (잠금 순서, 재귀 뮤텍스)

FanJae 2026. 5. 3. 23:14
[멀티 스레드 시리즈 글]

[Multi Thread] 1. 멀티 스레드를 이해하기 위한 기초 (프로그램, 프로세스, 스레드)
[Multi Thread] 2. 호출 스택과 실행 흐름
[Multi Thread] 3. 스레드는 어떻게 동작하는가 (컨텍스트 스위칭과 데이터 레이스)

[Multi Thread] 4. 경쟁 상태와 동기화 (원자성, 일관성, 뮤텍스)

[Multi Thread] 5. 교착 상태(Deadlock)와 해결 방법 (잠금 순서, 재귀 뮤텍스)

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)

- 위와 같은 상황은 재귀 뮤텍스를 사용하더라도 교착 상태가 발생할 수 있다.

- 따라서 재귀 뮤텍스를 사용하더라도 잠금 순서 규칙은 여전히 필요하다.

 

- 재귀 뮤텍스는 편리하지만, 같은 뮤텍스를 여러 번 잠그는 구조가 생겼다는 뜻이기도 하다.

- 따라서 설계가 복잡해졌다는 신호일 수 있으므로, 일반 뮤텍스로 구조를 단순하게 만들 수 있는지 먼저 검토하는 것이 좋다. 즉, 설계를 잘 신경써서 해야 한다.

 

 

 

 
 
 
 
Comments