[C++/CS] Deadlock 문제
데드락(Deadlock)은 두 개 이상의 프로세스나 스레드가 서로가 보유한 자원을 기다리면서 영원히 대기 상태에 빠지는 문제를 의미합니다. 즉, 서로가 서로를 기다리기 때문에 아무도 앞으로 진행하지 못하는 상태라고 볼 수 있습니다.
[C++/CS] Deadlock 문제
데드락(Deadlock)은 두 개 이상의 프로세스나 스레드가 서로가 보유한 자원을 기다리면서 영원히 대기 상태에 빠지는 문제를 의미합니다. 즉, 서로가 서로를 기다리기 때문에 아무도 앞으로 진행하지 못하는 상태라고 볼 수 있습니다.
멀티 스레드 환경에서 자원 잠금을 잘못 처리하면 쉽게 발생할 수 있는 대표적인 동시성 문제입니다.
데드락이 발생하기 위한 4가지 조건 (Coffman 조건)
데드락은 아래 4가지 조건이 모두 만족해야 발생합니다.
상호 배제(Mutual Exclusion)
- 자원을 동시에 사용할 수 없습니다. (예: mutex)
점유와 대기(Hold and Wait)
- 이미 자원을 보유한 상태에서 다른 자원을 기다립니다.
비선점(No Preemption)
- 다른 프로세스가 보유한 자원을 강제로 빼앗을 수 없습니다.
순환 대기(Circular Wait)
- 여러 프로세스가 서로가 보유한 자원을 기다리는 순환 구조가 형성됩니다.
이 중 하나라도 깨면 데드락을 방지할 수 있습니다. (중요)
데드락 예시: 두 개의 Mutex 잠금 순서 충돌
아래 예시는 데드락이 실제로 발생하는 C++ 코드입니다. ThreadA와 ThreadB가 서로 반대로 mutex를 잠금으로써 데드락이 발생합니다.
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
32
33
34
35
36
37
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
mutex m1;
mutex m2;
void ThreadA() {
cout << "[ThreadA] Lock m1\n";
m1.lock();
this_thread::sleep_for(chrono::milliseconds(100));
cout << "[ThreadA] Try Lock m2\n";
m2.lock(); // ThreadB가 m2를 잡고 있으므로 데드락
}
void ThreadB() {
cout << "[ThreadB] Lock m2\n";
m2.lock();
this_thread::sleep_for(chrono::milliseconds(100));
cout << "[ThreadB] Try Lock m1\n";
m1.lock(); // ThreadA가 m1을 잡고 있으므로 데드락
}
int main() {
thread t1(ThreadA);
thread t2(ThreadB);
t1.join();
t2.join();
return 0;
}
실행결과
이 상태에서 프로그램이 더 이상 진행되지 않았습니다.
데드락을 예방하는 방법
1) 자원 획득 순서를 통일하기
모든 스레드가 항상 같은 순서(A → B)로 자원을 잠그도록 합니다. 이렇게 하면 순환 대기 조건을 깨뜨릴 수 있습니다. => AB/BA 금지
2) std::scoped_lock 사용하기 (C++17 이상)
1
std::scoped_lock lock(m1, m2);
두 mutex를 데드락 없이 안전하게 한 번에 잠글 수 있습니다.
3) 타임아웃 기반 try_lock 사용
자원을 못 획득하면 아예 포기하거나 재시도하여 무한 대기를 피합니다.
This post is licensed under CC BY 4.0 by the author.
