지난 시간에 했던 것을 복습을 잠깐 해보자면
데드락 상황이 뭔지 알아 봤었는데
멀티쓰레드 환경이라 가정을 하자면
직원1, 직원2 이렇게 있을 때
화장실에 누가 먼저 가느냐에 따라서 먼저 사용하는 사람이 정해지는데
만약 직원1이 먼저 도착하면 자물쇠를 화장실에 잠구고 혼자 계속 사용하게 된다.
그런데, 만약 직원1이 자물쇠를 안풀고 나간다거나
문을 잠군 상태에서 안에서 잠들어 버리면
직원2는 그냥 하염없이 기다리면서 인생을 낭비하는 상황이니까
이 문제를 해결할 방법으로
Lock이라는 것을 사용하여
Lock( 자물쇠 )
문을 잠구고 사용하고 사용이 끝나면 자물쇠를 풀어 주는 작업을 자동을 해주는 해결책을 알아보았다.
그런데 이 Lock은 정말 정말 DeadLock 부분 중에서도 가장 기초적이고
간단한 경우에 속한다.
그래서 일반적인 상황에서 DeadLock이 발생하는 경우는
그렇게 간단한 상황에서 발생하는 것이 아니라
조금더 고차워적인 상황에서 발생을 한다.
예를들어 이렇게 자물쇠가 두개가 있다고 가정을 해보자
이 화장실에 들어가려면 자물쇠 두개가 필요하다고 가정을 하자.
그래서 이렇게 직원1, 직원2이가
직원1은 위쪽 자물쇠를 먼저 가져가려고해서 가지게 되었고
직원2는 아래쪽 자물쇠를 타겟팅을 먼저해서 가지고 가게되었는데
화장실안에 들어가기 위해서는 자물쇠 두개가 필요하기 때문에
직원1은 이제 아래쪽 자물쇠를
직원2는 이제 위쪽 자물쇠를 가지고 가려고하는데
이미 아래쪽은 직원2가 먼저 선점 한 상태이고
위쪽은 직원1이 선점을 한 상태라 가져올 수 없는 상황이라
꼬리물기식으로 둘다 화장실에 들어 갈 수 없는 상황이 발생을 한 것이다.
애당초 운명이 엇갈린 것이다.
그러니까 직원1은 위쪽을 먼저 잠구고 아래쪽을 잠구려고 시도를 했고
직원2(오른쪽)은 아래를 먼저 잠구고 오른쪽을 잠구려고 시도 한 것이다.
이렇게 잠구려고 하다보니까 이미 잠겨져 있는것이다.
위쪽 자물쇠는 직원1이 이미 잠군 상태라 직원2는 위쪽 자물쇠를 잠구지 못하는 상황인 것이다.
그래서 이런식으로 아무도 못들어가는
치명적인 상황이 발생하게 되는데
"이런" 경우가 생각보다 자주 일어나게 된다,
그리고
이런 상황이 일어나게된 근본적인 상황부터 살펴보면은
결국 자물쇠를 잠구는 순서가 안 맞기 때문이다.
직원1은 위쪽을 먼저 잠구고 아래쪽을 잠구려고 했고
직원2는 아래쪽을 먼저 잠구고 위쪽을 잠구려고 했기때문에 이런 사이클(문제)가 일어나게 된 것이다.
사실 이것을 고치는 방법은 간단한데
그냥 바로 "규약"을 정하는 것이다. 페어플레이를 하자.
규칙을 "위쪽 자물쇠를 먼저 잠구는 사람이 그 사람이 아래쪽 자물쇠도 잠구자!" 라고
일종의 규칙을 만드는 것이다.
그래서 이런 규약을 만들고 다시
화장실로 달려가게 해서
운이 좋게 직원1이 먼저 도착했다고 가정을 하면
사실상 게임이 여기서 끝나는 것이다.
그래서 지금은 화장실로 예를들어서 화장실에 자물쇠가 두개나 있으니까
좀 이상하다 but 코드에서는 비일비제 하게 일어난다.
코드로 한번 보러 가보도록 하자.
지금 같이 이렇게 간단한 경우에는 락을 두개씩 들고 있을 필요는 없지만
어느 정도의 규모가되는 MMO의 경우에는
이런 Lock같은 경우는 "클래스"안에 들어가게 되는 경우가 많다.
이렇게 session을 관리하는 매니저거나 유저를 관리하는 Manager일 경우
자기 자신만의 Lock을 이렇게 들고 있게될 것이다.
그런데 UserManager의 경우 어느 한 유져의 행동이 너무 이상하다 싶으면 "킥"을 해야되는데
그러면 SessionManager에 접근해야 할 일도 생길 수도 있을 것이고
반대로
SessionManager에서도 유져의 정보를 가져오기 위해서 UserManager에 접근을 둘다 서로 해야된다고 가정을 해보도록 하자.
그러면
서로 왔다리 갔다리 해야 되는 상황이 빈번하게 발생을 할텐데
코드로 예를 들어보자면
UserManager의 경우에는
Test()에서 자신의 _lock을 잡은다음에
이 안에서
SessionManager의 뭔가를 실행한다고 가정을 해보도록 하자.
조금 성의는 없지만
TestSession을 실행한다고 가정을 해보자.
거기서도 하필이면
이런식으로 자신의 락을 잡고있다고 가정을 해보자.
그리고 이런식으로 UserManager에서 자기 자신의 _lock을 잡은 상태에서
TestSession을 실행하게 되면은
이녀석을 먼저 잡은다음에
이녀석을 실행하는 식이 될 것이다.
그리고 또 반대로 얘기를 해보면
SessionManager에서도 마찬가지로
이녀석도 뭔가를 할 것이여서
Test()라는 애가 있는데
이녀석도 자신의 락을 잡고
UserManager에서 제공하는 뭔가를 실행을 한다고 가정을 해보자.
그리고
이런식으로 TestUser() 안에서도 락을 사용을 이렇게 하는 경우도 있을 것이다.
그래서 이렇게 이리저리 락을 사용하다보면은
이버젼에서는 내 락을 걸고 상대방 것을 실행하려고 하는 것인데
상대방입장에서 보면 똑같이 자신의 락을 건다음에
UserManager의 함수를 실행을 하려고 하는 것이다.
그래서 아까와 마찬가지로 순서가 뒤바뀌어가지고 서로 락을 획득을 하려는
"시도"를 할 것이다.
그러면 그 상황에서 DeadLock이 발생하는 지 테스트를 해보도록 하자.
SessionManager이고
userManager이다.
그리고 테스트를 위해서
실행을 해보면
이렇게하자.
그러면 각각의 쓰레드에서
하나는 SessionManager에서 Test를 호출을 하고
하나는 userManager에서 Test를 호출하다보면 언젠가는
꼬이는 현상이 발생할 것이다.
그래서 breakPoint를 잡고 이까지 잘 나오면 DeadLock없이 잘 나온 것이고
그게 아니러면 DeadLock 걸린 것이다.
실행을 하면 빠져 나오지 못하는 모습을 볼 수있다.
그리고 잠시 중단을 해보면
WaitAll에서 기다리고 있는 중이다.
그리고 각각의 쓰레드가 뭘 하고있냐 한번 보면은
UserManager는 지금
TestUser()라는 곳에서 물려있는 것을 볼 수 있다.
그리고 호출스택을 유심히 살펴보면은
SessionManager의 Test()를 호출 하려고 했었는데
SessionManager의 Test는
락을 잡은 상태에서
이렇게
UserManager.TestUser();
이쪽을 잡을려고 하는 상태였고,
SessionManager의 경우에는 반대로 일어나게되어
데드락 상황을 검증을 해보았다.
읽어봐라 좀 이해되었다 나는.
그러면 이제 데드락 상황을 해결하는 방안에 대해서 생각을 해보면
데드락 상황이 오면 어느정도의 시간이 걸리고 나서
포기하는 방법도 하나의 방법이다.
그리고 참고로
Monitor.Enter로 실패를 했을때를 가정을하고 코드를 짜는 게 과연 효율적인가? 를 생각해보면
반대이다.
그래서 데드락 같은 상황이 발생하면 (크래쉬가 나면)
그떄 고치는 것이 가장 좋다.
지금 같은 경우는 단순해서 쉽지만
경우에따라서 복잡한 경우가 있을 수도 있는데
이런 클래스가 몇십개씩 늘어날텐데
서로 어떤식으로 락을 걸고 할지 미리 다알고 프로그램을 짜는 경우는 없을 것이다.
그리고 사실 그런 기반 코드들은 이전 사람들이 만들어 놓은 경우가 많기 때문에
더욱더 lock이 어떤 순서로 호출되는 지 알기 힘들다는 것이다.
그래서 대부분의 경우 데드락이 발생하면 그 상황을 보고 고치는 경우가 많다.
그래서 데드락을 고치는 것은 많이 어렵지가 않다는 것이 일반적이기는 하다.
(예외적인 경우도 있다)
그리고 끔찍한 상황이 막상 개발할때는 문제가 없다가
라이브에 가서 문제가 터지는 경우가 많다.
그래서 QA단계에서는 전혀 일어나지 않다가 나중에 일어나는 경우가 많은데
보통 이렇게 쓰레드를 동시에 실행하지않고
User와 관련이 있는 경우가 있을때
이것을 실행하고
세션에서도 유저의 정보나 그런게 필요할때,
이런식으로 호출을 할것이라
동시에 이렇게 따닥 하고 실행하는 경우는 거의 잘 없고
띄엄띄엄 일어 날 것이다.
그래서
강제적으로 이렇게 맞춰주고 실행을 해보면
짜잔하고 데드락이 일어나지 않고 무사히 통과를 하였다!.
그래서
t1.Start(), t2.Start()가 정확하게 동시에 실행을 해야지만
아까와 같이 맞물리면서 데드락이 발생을 하는 것이다.
이런식으로 시간이 조금만 어긋나도
정상적으로 실행이 잘되는 것을 볼 수 있다.
그래서 그냥
런을 했을때 크래쉬가 날 수 있었는데
이런식으로 타이밍이 안맞게 되어서
크래쉬가 안났다면은
(게임이 라이브로 나가기 전에)
개발하는 동안에라도 발견 할 수 있으면 그 자체로도 좋다.
그래서 데드락은 생각보다 더 까다롭기 때문에
방지하는 것은 힘들고 실제로 발생을 하면 그때 고치는 것이 좋다.
이것이 오늘 결론이다.