본문으로 바로가기

데드락 vs 레이스 컨디션

counter++ 두 번이 1이 되는 잃어버린 갱신(레이스)과, 교차 잠금이 만드는 영원한 대기(데드락)를 각각 재현하고 처방(원자성·잠금 순서 통일)까지 실행한다.

1 / 8둘 다 '동시성 버그'로 불리지만 정반대의 병입니다. 먼저 레이스 — 스레드 두 개가 counter++를 한 번씩. 기대값은 2입니다.
레이스 — 조용히 틀린다데드락 — 시끄럽게 멈춘다
T1실행 중쥔 잠금: 없음
T2실행 중쥔 잠금: 없음
공유 counter = 0

counter++ = 읽기 → +1 → 쓰기, 세 동작

단계 목록 · 키보드 ←/→ 이동, Space 재생

레이스는 보호가 '없어서' 결과가 조용히 틀리는 병이고, 데드락은 보호(잠금)가 '얽혀서' 시끄럽게 멈추는 병이다 — 잠금을 늘리면 레이스는 줄고 데드락 위험은 늘어난다.

왜 헷갈리는가

둘 다 '동시성 버그'로 뭉뚱그려지지만 증상·원인·처방이 정반대다. 레이스는 재현이 안 돼서(타이밍 복권) 잡기 어렵고, 데드락은 재현되면 확실하지만 언제 걸릴지 모른다. "잠금을 걸었는데 왜 또 문제냐"는 질문의 답이 바로 이 대비다.

애니메이션이 보여주는 것

레이스 장면: counter++는 사실 읽기→+1→쓰기 세 동작이고, 그 사이에 스케줄러가 끼어들면 두 스레드가 같은 0을 읽는다. 최종값 1 — 예외도 로그도 없이 증가 하나가 증발한다. 실행마다 결과가 달라지는 것이 레이스의 지문이다.

데드락 장면: 잠금 두 개를 서로 반대 순서로 잡으면, 각자 하나씩 쥔 채 상대 것을 기다리는 원이 생긴다. 대기 그래프의 사이클 — 그래프 알고리즘 편의 사이클 감지가 여기서 실전이 된다. 처방은 전역 잠금 순서 통일: 순서가 같으면 원이 성립할 수 없다.

처방전 정리

병이 다르니 약도 다르다.

  • 레이스: 임계 구역을 뮤텍스로, 단순 카운터는 원자 연산으로, 더 근본적으로는 공유 가변 상태 자체를 줄이기(메시지 전달, 불변 데이터).
  • 데드락 4조건(상호 배제·점유 대기·비선점·순환 대기) 중 하나만 깨면 된다 — 실무 1순위는 순환 대기 제거(잠금 순서 통일).
  • 차선책: 타임아웃 있는 tryLock + 백오프(라이브락 주의), 한 번에 전부 획득, 잠금 대신 단일 스레드 큐로 직렬화.
  • 탐지 도구: 레이스는 ThreadSanitizer·Go race detector, 데드락은 DB의 데드락 감지(한 트랜잭션 강제 롤백)·JVM 스레드 덤프.
  • DB에서도 같은 병: 두 트랜잭션이 행 잠금을 교차로 잡으면 데드락 — DB는 감지 후 한쪽을 죽이므로, 애플리케이션은 재시도 로직이 정답이다.

기억할 것

  • 레이스 = 보호 부재 → 조용한 오답. 데드락 = 보호 얽힘 → 시끄러운 정지.
  • counter++조차 세 동작이다 — 원자성은 공짜가 아니다.
  • 데드락의 정의는 대기 그래프의 사이클 — 잠금 순서 통일이 원을 원천 차단한다.
  • 잠금을 늘릴수록 레이스↓ 데드락↑ — 이 균형이 동시성 설계다.
데드락 vs 레이스 컨디션 | KIE