레이스 컨디션과 데드락

공유 자원

  • 프로세스 혹은 스레드가 공유하는 자원
  • 다수의 프로세스 혹은 스레드가 동시에 공유 자원에 접근하는 경우 실행에 문제가 발생

임계 구역

  • 공유 자원에 접근하는 코드 중 동시에 실행했을 때 문제가 발생할 수 있는 코드를 임계 구역이라고 함

레이스 컨디션

  • 프로세스 혹은 스레드가 동시에 임계 구역의 코드를 실행하여 문제가 발생하는 상황
  • 자원의 일관성이 손상되며, 2개 이상의 프로세스 혹은 스레드가 임계 영역에 진입하려는 경우 하나는 작업이 끝날 때까지 대기해야 함
public class RaceCondition {
    static int sharedData = 0;

    public static void main(String[] args){
        Thread thread1 = new Thread(new Increment());
        Thread thread2 = new Thread(new Decrement());

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

        try {
            // 종료 대기
            thread1.join();
            thread2.join();
        } catch (InterruptedException e){
            System.out.println(e.getMessage());
        }

        System.out.println("Final value of sharedData: " + sharedData);
    }

    static class Increment implements Runnable {
        public void run() {
            for (int i = 0; i < 1000000; i++){
                sharedData++; // 공유 데이터 증가
            }
        }
    }

    static class Decrement implements Runnable {
        public void run() {
            for (int i = 0; i < 1000000; i++){
                sharedData--; // 공유 데이터 감소
            }
        }
    }
}

레이스 컨디션을 방지하기 위해선 프로세스와 스레드가 동기화되어야 하며, 동기화를 위해서는 실행 순서 제어, 상호 배제를 준수해야 합니다.

실행 순서 제어

  • 프로세스 및 스레드를 올바른 순서로 실행

상호 배제

  • 동시에 접근해서는 안 되는 자원에 하나의 프로세스 및 스레드만 접근

동기화 기법

  • 실행 순서와 상호 배제를 보장하기 위한 동기화 기법

뮤텍스 락

  • 동시에 접근해서는 안되는 자원에 동시 접근이 불가능하도록 상호 배제를 보장

임계 구역에 접근하고자 한다면 반드시 lock을 획득(acquire)해야 하고, 작업이 끝났다면 lock을 해제(release)해야 한다.

public class Mutex {
    static int sharedData = 0;
    static Lock lock = new ReentrantLock();

    public static void main(String[] args){
        Thread thread1 = new Thread(new Increment());
        Thread thread2 = new Thread(new Decrement());

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

        try {
            // 종료 대기
            thread1.join();
            thread2.join();
        } catch (InterruptedException e){
            System.out.println(e.getMessage());
        }

        System.out.println("Final value of sharedData: " + sharedData);
    }

    static class Increment implements Runnable {
        public void run() {
            for (int i = 0; i < 1000000; i++){
                lock.lock();
                try {
                    sharedData++; // 공유 데이터 증가
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    static class Decrement implements Runnable {
        public void run() {
            for (int i = 0; i < 1000000; i++){
                lock.lock();
                try {
                    sharedData--; // 공유 데이터 감소
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

세마포

  • 뮤텍스 락은 하나의 공유 자원을 고려하지만, 세마포어는 3개, 4개의 프로세스 및 스레드까지 특정 자원을 이용할 수 있는 상황에 사용
  • 공유 자원의 개수가 남아있을 때만 자원 접근 허용
  • 뮤텍스 락과 비슷하게 하나의 변수와 2개의 함수로 구성
    • 변수 S : 사용 가능한 자원의 수
    • wait() : 임계 구역 진입
    • signal() : 임계 구역 진입 후 호출
public class Semapho {
    static int sharedData = 0;
    static Semaphore semaphore = new Semaphore(1);

    public static void main(String[] args){
        Thread thread1 = new Thread(new Increment());
        Thread thread2 = new Thread(new Decrement());

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

        try {
            // 종료 대기
            thread1.join();
            thread2.join();
        } catch (InterruptedException e){
            System.out.println(e.getMessage());
        }

        System.out.println("Final value of sharedData: " + sharedData);
    }

    static class Increment implements Runnable {
        public void run() {
            for (int i = 0; i < 1000000; i++){
                try {
                    semaphore.acquire(); // 세마포어 획득
                    sharedData++; // 공유 데이터 증가
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    semaphore.release(); // 세마포어 해제
                }
            }
        }
    }

    static class Decrement implements Runnable {
        public void run() {
            for (int i = 0; i < 1000000; i++){
                try {
                    semaphore.acquire(); // 세마포어 획득
                    sharedData--; // 공유 데이터 감소
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    semaphore.release(); // 세마포어 해제
                }
            }
        }
    }
}

조건 변수

  • 동기화 기법 중 하나
  • [조건 변수] : 실행 순서 제어를 위한 동기화 도구로 프로세스나 스레드의 실행 순서를 제어
    • wait() : 호출한 프로세스 및 스레드의 상태를 대기 상태로 전환
    • signal() : wait()로 일시 중지된 프로세스 및 스레드의 실행을 재개하는 함수
  • wait() : 호출한 프로세스 및 스레드의 상태를 대기 상태로 전환
  • signal() : wait()로 일시 중지된 프로세스 및 스레드의 실행을 재개하는 함수

모니터

  • 공유 자원과 그 공유 자원을 다루는 함수로 구성된 동기화 도구
  • 프로세스 및 스레드는 공유 자원에 접근하기 위해 반드시 정해진 공유 자원 연산(인터페이스)를 통해 모니터로 진입해야 함

스레드 안전

  • 멀티 스레드 환경에서 어떤 변수나 함수, 객체에 동시 접근이 이루어져도 실행에 문제가 없는 상태

교착 상태

  • 일어나지 않을 사건을 기다리며 프로세스의 진행이 멈춰 버리는 현상
  • 4가지 발생 조건(상호 배제, 점유와 대기, 비선점, 원형 대기)을 모두 만족해야 발생

상호 배제

  • 한 번에 하나의 프로세스만 해당 자원을 이용 가능하기 때문에 발생
  • 한 프로세스가 사용하는 자원을 다른 프로세스가 사용할 수 없음

점유와 대기

  • 할당받은 상태에서 다른 자원을 할당받기를 기다림

비선점

  • 해당 자원을 이용하는 프로세스의 작업이 끝나야만 자원을 이용할 수 있음

원형 대기

  • 서로 점유한 자원을 할당받기 위해 원의 형태로 대기

교착 상태 해결 방안

  • 발생 조건에 부합하지 않도록 자원을 분배하는 방식으로 예방할 수 있음
  • 교착 상태의 위험이 있다면 자원을 할당하지 않는 방식으로 회피할 수 있음
  • 교착 상태를 검출한 후 회복할 수 있음

교착 상태 예방

  • 교착 상태를 발생시키는 4가지 필요 조건 중 하나를 충족하지 못하게 하는 방법
    • 모든 자원에 번호를 매기고 오름차순 할당 등
  • 모든 자원에 번호를 매기고 오름차순 할당 등

교착 상태 회피

  • 교착 상태를 한정된 자원의 무분별한 할당으로 인해 발생하는 문제로 정의
  • 이를 발생하지 않도록 하는 방법, 자원이 많다면 위험 증가

교착 상태 검출 후 회복

  • 발생을 인정하고 처리하는 사후 조치
  • 자원 선점을 통해 회복, 강제 종료