프로그래밍/시스템
Deadlock(교착 상태) - 8. Deadlock을 피하기 위한 실전 코딩 기법
개발_노트
2025. 3. 26. 15:24
8. Deadlock을 피하기 위한 실전 코딩 기법
데드락은 코드 구조와 동시성 제어 방식에 따라 충분히 예방하거나 회피할 수 있는 문제입니다.
실제 개발 환경에서 자주 활용되는 대표적인 데드락 회피 기법들을 언어별 예시와 함께 정리합니다.
✅ 1. 락 순서 지정 및 일관성 유지
● 개념
여러 자원을 동시에 사용할 경우, 모든 프로세스/스레드가 동일한 락 획득 순서를 따르도록 설계하면
환형 대기(Circular Wait)를 예방할 수 있습니다.
● 예시
Python
lock_a.acquire()
try:
lock_b.acquire()
try:
# 작업 수행
finally:
lock_b.release()
finally:
lock_a.release()
Java
synchronized(lockA) {
synchronized(lockB) {
// 작업 수행
}
}
C++ (C++11 이상)
std::lock(lock_a, lock_b); // 교착 방지
std::lock_guard<std::mutex> guard_a(lock_a, std::adopt_lock);
std::lock_guard<std::mutex> guard_b(lock_b, std::adopt_lock);
// 작업 수행
✅ 2. 타임아웃 기반 락 획득
● 개념
지정된 시간 내에 락을 획득하지 못하면 실패로 간주하여 무한 대기를 방지하고, 대체 흐름으로 처리할 수 있도록 합니다.
● 예시
Python
if lock.acquire(timeout=2):
try:
# 작업
finally:
lock.release()
else:
print("락 획득 실패: 대체 로직 수행")
Java
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// 작업
} finally {
lock.unlock();
}
} else {
System.out.println("락 획득 실패");
}
C++ (std::timed_mutex)
if (mutex.try_lock_for(std::chrono::seconds(2))) {
// 작업
mutex.unlock();
} else {
std::cout << "락 획득 실패\n";
}
✅ 3. 재시도(Retry) 및 백오프(Backoff) 전략
● 개념
락 획득에 실패했을 때 즉시 재시도하지 않고, 일정 시간 대기 후 점진적으로 재시도 간격을 늘리며 충돌을 완화하는 전략입니다.
● 예시 (Python)
import time, random
for attempt in range(5):
if lock.acquire(timeout=1):
try:
break
finally:
lock.release()
else:
time.sleep(random.uniform(0.1, 0.3) * (2 ** attempt))
● 예시 (Java)
for (int attempt = 0; attempt < 5; attempt++) {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
break;
} finally {
lock.unlock();
}
} else {
Thread.sleep(100 * (1 << attempt)); // 100ms, 200ms, 400ms...
}
}
✅ 4. 로깅 및 모니터링을 통한 데드락 감지
● 개념
실행 중 데드락이 발생할 가능성을 조기에 파악하기 위해
락 획득/해제, 스레드 상태, 자원 점유 현황을 기록하거나 모니터링 시스템과 연동합니다.
● 활용 방법
- 락 대기 시간이 일정 이상이면 경고 로그 출력
- 주기적인 스레드 덤프 수집 (jstack, pstack, faulthandler 등)
- 락 사용 패턴 시각화 (Grafana, Elastic APM, Jaeger 등)
✅ 5. 분산 시스템 / 마이크로서비스 환경에서의 방지 전략 (심화)
● 문제 특성
서비스 간 API 호출, 분산 자원 점유, 네트워크 지연 등으로 인해
노드 간 순환 대기 구조가 형성되어 데드락 발생 위험이 존재합니다.
● 실용 전략
전략 | 설명 |
분산 락 도입 | Redis, ZooKeeper, etcd 등을 통해 중앙에서 자원 잠금 관리 |
락 TTL 설정 | 락에 만료 시간을 지정하여 무한 점유 방지 (SET NX EX 등) |
호출 순서 고정 | 마이크로서비스 간 의존 순서를 명확히 정의 |
분산 트레이싱 | OpenTelemetry, Jaeger로 서비스 간 호출 흐름을 시각화하고 순환 대기 감지 |
● 간단 예시 (Redis 기반 분산 락)
SET lock:key client_id NX EX 5 # 락 획득: 5초 TTL
📝 요약
기법 | 핵심 목적 | 적용 대상 |
락 순서 고정 | 환형 대기 제거 | 멀티스레드, 공유 자원 |
타임아웃 락 | 무한 대기 차단 | 서버 사이드 동시성 처리 |
재시도/백오프 | 충돌 완화, 라이브락 방지 | 병렬 처리, API 요청 |
로깅/모니터링 | 사후 분석 및 조기 감지 | 운영 환경, 장애 대응 |
분산 락 관리 | 서비스 간 자원 충돌 회피 | 마이크로서비스, 클러스터 환경 |
데드락을 막기 위한 전략은 단일 방식이 아닌 설계-코드-운영 전 단계에 걸친 종합적 접근이 필요합니다.
특히 분산 시스템 환경에서는 단순한 코드 패턴만으로는 충분하지 않으며,
호출 흐름, 자원 사용 정책, 트랜잭션 범위까지 고려한 설계가 중요합니다.