프로그래밍/시스템

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 요청
로깅/모니터링 사후 분석 및 조기 감지 운영 환경, 장애 대응
분산 락 관리 서비스 간 자원 충돌 회피 마이크로서비스, 클러스터 환경

데드락을 막기 위한 전략은 단일 방식이 아닌 설계-코드-운영 전 단계에 걸친 종합적 접근이 필요합니다.
특히 분산 시스템 환경에서는 단순한 코드 패턴만으로는 충분하지 않으며,
호출 흐름, 자원 사용 정책, 트랜잭션 범위까지 고려한 설계가 중요합니다.