2 분 소요

이 질문은 내가 여기저기 입사지원을 하고 면접을 보고 시험을 볼 때마다 매번 나왔던 질문과 문제들이다. 처음 한번은 이 문제에 대답을 못했고 그다음 다시 공부한 다음 다음번부터는 잘 대답했다.

신기한건 이 질문은 내가 면접을 본 몇군데 회사의 기술면접, 실기, 필기시험 때마다 매번 나온 문제였다. 프로그래머라면 반드시 알아야할 문제라고 생각해두자.

2026년 수정 안내 처음 썼던 “두 스레드가 서로의 실행 결과를 기다리는 현상”이라는 정의는 부정확했다. 데드락의 핵심은 실행 결과가 아니라 자원(락)에 대한 순환 대기다. 정의를 바로잡고, 면접에서 실제로 요구하는 코프만 조건까지 정리한다.


데드락(교착 상태)이란

두 개 이상의 스레드(또는 프로세스)가 서로가 점유한 자원을 기다리며, 어느 쪽도 진행하지 못하는 상태를 말한다.

가장 흔한 형태는 락 획득 순서가 엇갈리는 경우다.

std::mutex mtx_a;
std::mutex mtx_b;

void Thread1()
{
    std::lock_guard<std::mutex> lock_a(mtx_a);   // A를 잡고
    std::lock_guard<std::mutex> lock_b(mtx_b);   // B를 기다린다
}

void Thread2()
{
    std::lock_guard<std::mutex> lock_b(mtx_b);   // B를 잡고
    std::lock_guard<std::mutex> lock_a(mtx_a);   // A를 기다린다
}

Thread1이 A를 잡은 직후 Thread2가 B를 잡으면, 둘 다 상대가 쥔 락을 영원히 기다린다. 여기서 두 스레드는 “죽지 않고 남는” 것이 아니라 블록(대기) 상태로 멈춰 있다. 프로세스는 살아 있지만 아무 일도 하지 않는다.


데드락의 네 가지 필요조건 (코프만 조건)

면접에서 실제로 듣고 싶어 하는 답은 대개 이것이다. 아래 네 조건이 동시에 성립할 때만 데드락이 발생한다.

  1. 상호 배제 (Mutual Exclusion) — 자원을 한 번에 하나의 스레드만 점유할 수 있다.
  2. 점유와 대기 (Hold and Wait) — 자원을 쥔 채로 다른 자원을 기다린다.
  3. 비선점 (No Preemption) — 점유한 자원을 강제로 빼앗을 수 없다.
  4. 순환 대기 (Circular Wait) — 대기 관계가 원형을 이룬다. A는 B를, B는 C를, C는 A를 기다리는 식.

바꿔 말하면, 이 중 하나만 깨뜨려도 데드락은 발생하지 않는다. 이게 예방 기법의 출발점이다.


실무에서의 예방 방법

  • 락 순서를 전역으로 고정한다. 가장 현실적이고 효과적인 방법이다. 순환 대기 조건을 깨는 것이다. 예를 들어 “항상 ID가 작은 객체의 락을 먼저 잡는다”처럼 규칙을 정한다.
  • 여러 락을 한 번에 잡는다. C++이라면 std::scoped_lock(C++17)이나 std::lock을 쓴다. 내부적으로 데드락을 피하는 순서로 획득해준다.

    std::scoped_lock lock(mtx_a, mtx_b);  // 순서와 무관하게 안전하다
    
  • 타임아웃을 둔다. try_lock_for 등으로 일정 시간 안에 못 잡으면 포기하고 물러난다. 비선점 조건을 완화하는 방식이다.
  • 락을 쥔 채로 외부 코드를 호출하지 않는다. 콜백이나 가상 함수를 락 안에서 부르면, 그 안에서 무슨 락을 잡을지 알 수 없다.
  • 락의 범위를 최소화한다. 잡고 있는 시간이 짧을수록 충돌 확률이 낮아진다.

여기서 주의할 것은, std::lock_guard 같은 RAII 래퍼는 언락 누락을 막아줄 뿐 데드락을 막아주지는 않는다는 점이다. 위 예제 코드도 전부 lock_guard를 쓰고 있지만 데드락이 난다.


관련해서 헷갈리기 쉬운 것들

  • 라이브락(Livelock) — 상태는 계속 바뀌는데 아무도 진행하지 못하는 상황. 서로 양보하다가 계속 부딪히는 경우다. 대기하지 않고 CPU를 쓴다는 점이 데드락과 다르다.
  • 기아(Starvation) — 특정 스레드만 계속 자원을 얻지 못하는 상황. 다른 스레드는 진행하므로 데드락은 아니다.

여담

특히 모 게임회사에서 네트워크 게임을 만들 때 데드락 현상에 빠지지 않았느냐고 물어봤는데 아직까지는 그런적이 없다고 말했더니 면접관분이 갸우뚱하셨다.

당시 “윈도우 소켓이 알아서 처리해주기 때문”이라는 설명을 들었는데, 지금 보면 근거가 없는 이야기다. Winsock은 데드락을 막아주지 않는다. 당시 내가 데드락을 겪지 않았던 진짜 이유는 싱글 스레드에 가까운 구조로 짰고, 여러 개의 락을 중첩해서 잡는 코드가 없었기 때문일 것이다. 락이 하나뿐이면 순환 대기가 성립할 수 없다.

이후 IOCP 기반으로 워커 스레드를 여러 개 돌리는 구조를 만들면서 락 순서 문제를 실제로 겪게 되었다.

댓글 남기기