코다람쥐 2022. 3. 10. 21:30

1. 자물쇠가 2개인 경우

 

1번쓰레드가 1번자물쇠를 가지고있고

2번쓰레드가 2번자물쇠를 가지고있다고 가정했을 때

 

1번쓰레드가 1번자물쇠를 가진상태에서 2번자물쇠에 접근하려고 대기하고,

2번쓰레드가 2번자물쇠를 가진상태에서 1번자물쇠에 접근하려고 대기하면 교착상태가 발생한다.

데드락발생

 

위의 데드락을 방지하기 위해서는 쓰레드끼리 1번자물쇠부터 먼저 접근하고 그 다음에 2번자물쇠에 접근하게끔 설정해주면된다. 그러면 먼저 도착한 쓰레드가 1번자물쇠를 사용하고 락을 해제하면 다음에 도착한 쓰레드가 1번을 사용하게 되기 때문에 교착상태가 발생하지 않는다.

 

코드로 살펴보면 아래와 같다.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{
    class SessionManager
    {
        static object _lock = new object();
        public static void TestSession()
        {
            lock (_lock)
            {

            }
        }
        public static void Test()
        {
            lock (_lock)
            {
                UserManager.TestUser();
            }
        }
    }

    class UserManager
    {
        static object _lock = new object();
        public static void Test()
        {
            lock (_lock)
            {
                SessionManager.TestSession();
            }
        }

        public static void TestUser()
        {
            lock (_lock)
            {

            }
        }
    }

    class Program
    {
        static int number = 0;
        static object _obj = new object();
        
        static void Thread_1()
        {
            for (int i = 0; i < 10000; i++)
            {
                SessionManager.Test();
            }            
        }

        static void Thread_2()
        {
            for (int i = 0; i < 10000; i++)
            {
                UserManager.Test();
            }
        }
        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);
            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine("빠져나옴!");
        }
    }
}

실제로 코드를 실행해보면 교착상태에 빠지는 것을 알 수 있다.

 

2. 해결방법

방법1. Monitor.TryEnter

Monitor.TryEnter(_lock, 100);

0.1초동안 락을 획득하지 못하면 다시 돌아오는 방법이다.

그런데 애초에 교착상태가 발생했다는 것은 구조적으로 문제가 있는 것이기 때문에 그냥 새롭게 고치는 방법을 권유한다.

 

방법2. Thread.Sleep()

static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);
            t1.Start();

            Thread.Sleep(100); // 0.1초 후 실행

            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine("빠져나옴!");
        }

동시에 락을 서로 획득하려고 하면 교착상태가 발생하기 때문에 0.1초의 시간차를 줘서 데드락을 막는 방법이다.

 

사실 미리 방지하는 방법은 별로 없고 그냥 크래시가 날때마다 데드락을 확인하는 방법이 유일하다.