Right now do !

[JAVA] concurrent programming - 교착상태(Dead Lock)

by 지금당장해

프롤로그

 필자는 전산학을 공부했다. 요즘은 컴퓨터 공학이라고 하는데 틀린말이다. 우린 컴퓨터를 만드는 법을 배우지 않았다. 컴퓨터 사이언스가 맞다. 필자의 사견이니 학과명 변경을 주도하시고 찬성하신 "교수님들 노여워 마세요!" 왜 전공 운운 했냐면 교착상태/Dead Lock 학교다닐때 다 배운거라는 것이다. 그런데 오랜 세월 요즘 말로 하면 프로트엔드 그것도 웹이 아닌 VC++,C#으로 Native 프로그램을 주로 해오다 보니 Thread를 만들더라도 그저 UI에 Lock이 안걸리게 하는 수준 이였다. 그런데 요즘 서버에서 동작하는 서비스 프로그램을 작성하다 보니 불량 Thread를 검출해야 하는 미션이 생겼다. 그러다 보니 소위 DeadLock이 되는 상황을 만들어 놓고 Test case를 돌리려 하는데 문득 교착상태를 어떻게 만들지 후딱 만들지 못했다. "아~~~ 학교때 배웠는데..." 그렇다 오늘을 교착상태 영어로 DeadLock을 다뤄보려 한다.

 

교착상태의 정의

 교착 상태(膠着狀態, 영어: deadlock)란 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태를 가리킨다. 예를 들어 하나의 사다리가 있고, 두 명의 사람이 각각 사다리의 위쪽과 아래쪽에 있다고 가정한다. 이때 아래에 있는 사람은 위로 올라 가려고 하고, 위에 있는 사람은 아래로 내려오려고 한다면, 두 사람은 서로 상대방이 사다리에서 비켜줄 때까지 하염없이 기다리고 있을 것이고 결과적으로 아무도 사다리를 내려오거나 올라가지 못하게 되듯이, 전산학에서 교착 상태란 다중 프로그래밍 환경에서 흔히 발생할 수 있는 문제이다. - 발췌: 위키피디아 한글판-

https://ko.wikipedia.org/wiki/%EA%B5%90%EC%B0%A9_%EC%83%81%ED%83%9C

 

교착 상태 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 데드락은 여기로 연결됩니다. 다른 뜻에 대해서는 데드락 (동음이의) 문서를 참조하십시오. 교착 상태(膠着狀態, 영어: deadlock)란 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태를 가리킨다. 예를 들어 하나의 사다리가 있고, 두 명의 사람이 각각 사다리의 위쪽과 아래쪽에 있다고 가정한다. 이때 아래에 있는 사람은 위로 올라 가려고 하고, 위에 있

ko.wikipedia.org

 이 위키 문서에 보면 방지하는 방법 회피하는 방법 여러가지를 이야기 하고 있는데 꼭 읽어보기 바란다. 

 

교착상태를 만들어 보자

 이 구조에서 Object A와 Obejct B는 같은 Class의 인스턴스이다. 구현에 따라 인스턴스별로 각기 다른 Lock을 갖게 할 수 있다. test1() test2() method는 synchronized로 선언되어 객체(this)를 Lock 객체로 사용하는 임계영역을 가지고 있다.

교착상태 시뮬레이션

 

Tread 1 가 Object A 의 임계영역에 진입을 한 후 임계영역을 탈출하지 않은 상태에서 Object B의 임계영역에 진입하려 한다. 그런데 이때 이미 Thread 1는 Object B의 임계영역에 진입 한 상태가 되어 Thread 1 은 Object B 의 임계역역 시작 지점에서 BLOCKED 된다. 이 상황에서 Object B의 임계영역에 진입한 Thread 2는 Thread 1이 Lock 을 확보한 Object A의 임계 영역을 진입하려 한다. 이 역시 BLOCKED 된다. 양 Thread 는 영원한 BLOCKED 상태에 들어갔다.

JAVA 코드로 작성

public class DeadLockTest {

    @Test
    public void runDeadLock() {
        Shared s1 = new Shared();
        Shared s2 = new Shared();

        ExecutorService es = Executors.newFixedThreadPool(3);
        Future<?> future1 = es.submit(new Task(s1, s2));
        Future<?> future2 = es.submit(new Task(s2, s1));

        es.shutdown();

        try {
            int awaitCnt = 0;
            while (!es.awaitTermination(10, TimeUnit.SECONDS)) {
                System.out.println("Not a terminate threads. ");
                reportDeadLockThread();
                awaitCnt++;
                if ( awaitCnt > 3 && !future1.isCancelled() && !future2.isCancelled() ) {
                    // (4)                                     
                    future1.cancel(true);
                    future2.cancel(true);
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

	// (5)
    private void reportDeadLockThread() {
        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        // Returns null if no threads are deadlocked.
        long[] threadIds = bean.findDeadlockedThreads();
        if (threadIds != null) {
            ThreadInfo[] infos = bean.getThreadInfo(threadIds);
            for (ThreadInfo info : infos) {
                String s = info.getThreadName();
                System.out.println(String.format("DeadLocked Thread Name >>>> %s", s));
            }
        }
    }

    static class Shared
    {
        // (1)
        synchronized void test1(Shared s2)
        {
            System.out.println("test1-begin");
            ThreadUtil.sleep(1000);
            // (2)
            s2.test2(this);
            System.out.println("test1-end");
        }

		// (3)
        synchronized void test2(Shared s1)
        {
            System.out.println("test2-begin");
            ThreadUtil.sleep(1000);

            // taking object lock of s1 enters
            // into test1 method
            s1.test1(this);
            System.out.println("test2-end");
        }
    }


    static class Task implements Runnable
    {
        private Shared s1;
        private Shared s2;

        // constructor to initialize fields
        Task(Shared s1, Shared s2)
        {
            this.s1 = s1;
            this.s2 = s2;
        }

        // run method to start a thread
        @Override
        public void run()
        {
            // taking object lock of s1 enters
            // into test1 method
            s1.test1(s2);
        }
    }
}

 이 전 단원의 다이어그램을 코드로 옮기면 위와 같다. 코드를 돌려보면 이해가 제일 빠르다. 다만 코드가 간단한 듯 하지만 사전 지식에 따라 이해가 어려울수도 있으니 약간의 설명을 첨한다. 다이어 그램에서 Object A라고 칭한것이 Shared s1으로 선언한 변수이다. 나머지도 이런 식이니 잘 매칭해서 보기 바란다.

 

(1) runDeadLock 함수를 통해서 thread 1, thread 2가 거의 동시에 시작할 것이다. 각 thread는 test1() 함수의 실행을 시작한다. this의 동기화 블럭이 시작되면서 자연스레 Lock객체를 공유하는 test2()도 Lock이 걸린다.

 

(2) 다른객체 즉 s2객체의 test2() 함수를 호출한다. 다른 thread도 동시에 출발 했기 때문에 이 함수는 이미 Lock이 걸려 있을 것이다. 이 호출 thread는 이 지점에서 BLOCKED 된다. 

 

(3) test2() 함수에 sychronized로 선언되어 있지 않다면 아무 문제 없으나 (1)항에서 설명 하였듯이 이 선언으로 test1() 함수와 Lock이 바꿔말하면 임계영역이 똘똘말이가 되어 있다. 해서 같은 객체 내에서 하나의 호출이 일어나면 다른 함수도 다른 thread가 호출할 수 없다는 뜻이다.

 

(4) 이 thread들을 10초씩 3번정도 기다려 볼 생각이었는데 답이 없어 해당 thread를 cancel을 했다. 그러나 아무 효과가 없다. 암묵적인 Lock이 BLOCKED 되면 중단시킬 방법이 없기 때문이다. 

 

(5) Dead Lock을 검출하는 방법이다. 참고 바란다.

에필로그

이번 글에서 제공한 TEST코드를 수행시키면 무한히 실행 할 것이다. 이번 목표를 달성한것이다. DeadLock이 무엇인지 정의하고 정의한대로 설계하여 구현 했다. 다음에 글에서는 명시적 Lock을 소개하면서 이 코드를 개선하여 DeadLock에 빠진 thread를 세워볼 것이다.

블로그의 정보

지금 당장 해!!!

지금당장해

활동하기