교착 상태(Deadlock)란 무엇이며, 이를 예방하거나 해결하는 방법은 무엇인가요?

김상욱·2024년 11월 30일
0

교착 상태(Deadlock)란 무엇이며, 이를 예방하거나 해결하는 방법은 무엇인가요?

교착 상태(DeadLock)은 두 개 이상의 프로세스가 서로 자원을 점유한 상태에서, 다른 프로세스가 점유한 자원을 요청하며 무한히 대기하게 되는 상태. 이 상태에서는 더 이상 어떤 프로세스도 작업을 진행할 수 없으므로 시스템 일부 또는 전체가 멈춤.

4가지 필요 조건(Coffman 조건)
1. 상호 배제(Mutual Exclusion) : 자원은 한번에 하나의 프로세스만 사용할 수 있어야 함.
2. 점유 및 대기(Hold and Wait) : 자원을 점유한 프로세스가 다른 자원을 요청하며 대기 상태에 있어야 함.
3. 비전섬(No Preemption) : 프로세스가 점유한 자원을 강제로 빼앗을 수 없음.
4. 순환 대기(Circular Wait) : 프로세스들이 순환 구조로 자원을 요청하며 대기함.

예방 방법
1. 상호 배제 조건 제거 : 자원의 공유를 가능하게 하거나, 공유 자원한 자원을 설계. 하지만 대부분의 자원(프린터, 파일 쓰기 등)은 본질적으로 상호 배제가 필요.
2. 점유 및 대기 조건 제거 : 프로세스가 자원을 요청할 때 이미 모든 자원을 점유한 상태에서 실행을 시작하도록 설계. 자원이 부족할 경우 프로세스가 아무 자원도 점유하지 않고 대기 상태로 들어가도록 설계.
3. 비선점 조건 제거. 한 프로세스가 자원을 점유하고 있는 동안 다른 프로세스가 이를 요청하면 점유 중인 프로세스가 자원을 반환하도록 설계.
4. 순환 대기 조건 제거 : 자원에 번호를 부여하고 항상 낮은 번호에서 높은 번호로만 자원을 요청하도록 규칙을 정함. 이를 통해 순환 구조가 발생하지 않도록 방지.

해결방법
1. 교착 상태 탐지 및 복구(Detection and Recovery) : 교착 상태를 탐지하기 위해 자원 할당 그래프를 사용하거나, 교착 상태 탐지 알고리즘 활용
: 자원 할당 그래프는 프로세스와 자원의 관계를 시각적으로 표현하여 자원의 점유와 요청 상태를 파악할 수 있다. 이를 통해 사이클이 탐지되면 교착 상태임을 알 수 있다.
2. 자원 할당 방지(Banker's Algorithm) : 시스템이 프로세스의 자원 요청을 허용하기 전에 요청을 허용했을 때 안전 상태가 유지되는지 확인
3. 타임아웃(Timeouts) : 프로세스가 일정 시간 동안 자원을 점유하지 못하면 요청을 취소하고 다시 시도하도록 설계.
4. 프로세스 우선순위 조정 : 프로세스에 우선순위를 부여하여 특정 프로세스가 자원을 계속 점유하거나 기다리게 되는 상황을 방지.


1. 멀티스레드 환경에서 교착 상태 구현

교착 상태가 발생하는 코드를 작성하고 이를 예방하거나 해결하는 코드를 작성해 볼 수 있습니다.

public class DeadlockExample {
    private final Object resource1 = new Object();
    private final Object resource2 = new Object();

    public static void main(String[] args) {
        DeadlockExample example = new DeadlockExample();

        Thread thread1 = new Thread(() -> example.method1());
        Thread thread2 = new Thread(() -> example.method2());

        thread1.start();
        thread2.start();
    }

    public void method1() {
        synchronized (resource1) {
            System.out.println("Thread 1: Locked resource 1");

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (resource2) {
                System.out.println("Thread 1: Locked resource 2");
            }
        }
    }

    public void method2() {
        synchronized (resource2) {
            System.out.println("Thread 2: Locked resource 2");

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (resource1) {
                System.out.println("Thread 2: Locked resource 1");
            }
        }
    }
}
  • 실행 결과: Thread 1Thread 2가 각각 자원 resource1resource2를 점유한 후 서로 상대방의 자원을 요청하며 교착 상태에 빠집니다.

2. 교착 상태 예방 코드 작성

위 코드에서 자원 요청 순서를 정해 순환 대기 조건을 제거해 봅니다.

public void method1() {
    synchronized (resource1) {
        System.out.println("Thread 1: Locked resource 1");

        synchronized (resource2) {
            System.out.println("Thread 1: Locked resource 2");
        }
    }
}

public void method2() {
    synchronized (resource1) { // resource1을 먼저 점유하도록 변경
        System.out.println("Thread 2: Locked resource 1");

        synchronized (resource2) {
            System.out.println("Thread 2: Locked resource 2");
        }
    }
}

3. Spring 프로젝트에서의 교착 상태 예방

Spring에서 트랜잭션(Transaction) 관리 중 잘못된 자원 잠금으로 교착 상태가 발생할 수 있습니다. 이를 해결하기 위한 방법을 연습할 수 있습니다.

  • 트랜잭션 격리 수준 설정:
    교착 상태를 방지하려면 트랜잭션 격리 수준을 적절히 설정합니다. 예를 들어, @Transactional 어노테이션을 사용합니다.
@Transactional(isolation = Isolation.SERIALIZABLE)
public void performTransaction() {
    // 데이터베이스 작업 수행
}
  • 동시성 관리 연습:
    @Lock과 같은 JPA 기능을 사용해 동시 자원 접근을 제한합니다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
public Optional<Entity> findByIdWithLock(Long id) {
    return repository.findById(id);
}

4. 교착 상태를 탐지하는 유틸리티 구현

Java에서 ThreadMXBean을 사용해 교착 상태를 탐지하는 코드 작성.

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class DeadlockDetection {
    public static void main(String[] args) {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();

        if (deadlockedThreads != null) {
            ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
            System.out.println("Deadlocked threads detected:");
            for (ThreadInfo info : threadInfos) {
                System.out.println(info.getThreadName());
            }
        } else {
            System.out.println("No deadlocked threads detected.");
        }
    }
}

실습을 통해 얻을 수 있는 학습 포인트

  1. 멀티스레드 프로그래밍 이해:
    • 동기화 메커니즘(synchronized, Lock 등)을 활용.
    • 교착 상태가 발생하는 원리 학습.
  2. 교착 상태 예방 및 해결 능력 강화:
    • 자원 요청 순서, 타임아웃, 트랜잭션 격리 수준 설정 등 실습.
  3. 실제 개발 환경에서의 문제 해결 능력:
    • Spring의 동시성 관리 및 트랜잭션 처리에 적용.

위 실습을 통해 멀티스레드 환경에서의 교착 상태를 실질적으로 이해하고, Spring 기반 백엔드 개발에서도 발생할 수 있는 문제를 예방 및 해결하는 경험을 쌓을 수 있습니다.

0개의 댓글