본문 바로가기
게임 서버/Thread

[c#/유니티] 임계영역(Critical Section)을 위한 Monitor와 Lock

by 얘리밍 2022. 12. 7.
320x100
728x90

 

멀티 스레드 환경에서 같은 객체를 여러 곳에서 호출하는 경우

예기치 않은 결과가 나타날 확률이 높다. 

 

 

 

 📌  Monitor 활용

             상호 배제(Mutual Exclusive)를 이뤄 다른 스레드의 접근막도록 한다

             Enter Exit로 구현하며 Enter는 문을 잠그는 행위, Exit는 문을 여는 행위라 생각하면 된다. 

 

 

 

            Thread 1에서 Monitor.Enter(_obj)를 통해 임계 영역(Critical Section)에 들어가면, 

            Thread2는 Thread 1에서 Monitor.Exit(obj)로 해제할 때 까지 해당 영역에 접근하지 못한다. 

 

    *임계 영역 : 여러 스레드가 공유 자원에 접근할 때, 하나만 접근할 수 있도록 보장해 주는 영역. 

    static int number = 0;   //공유 자원 
    static object _obj = new object();   //좌물쇠 역할

    static void Thread_1()
    {
        for (int i = 0; i < 100000; i++)
        {
            /*상호배체 Mutual Exclusive */
            Monitor.Enter(_obj); //문을 잠그는 행위 -> 다른 이 들어올 수 없음 
            number++;
            Monitor.Exit(_obj);  //문을 다시 엶 -> 다른 이 접근 가능 
        }
    }

    static void Thread_2()
    {
        for (int i = 0; i < 100000; i++)
        {
            Monitor.Enter(_obj);
            number--;
            Monitor.Exit(_obj);
        }
    }

    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(number);
    }
}

 

        만약, Moniter.Exit를 통해 문을 열지 않고 나가버리면 어떤 상황이 발생할까..

 

            👉 데드락(DeadLock)이 발생한다. 

 

 

 

 📌  DeadLock이란? 

            무한히 다음 자원을 기다리는 상태.

            상대방의 작업이 끝나기 만을 기다리고 있는 상태로 결과적으로 아무것도 완료되지 못하는 상태이다. 

 

 

    static int number = 0;   //공유 자원 
    static object _obj = new object();   //좌물쇠 역할

    static void Thread_1()
    {
        for (int i = 0; i < 100000; i++)
        {
            /*상호배체 Mutual Exclusive */
            Monitor.Enter(_obj); 
            {
                number++;
                return;       //문을 열지 않고 그냥 빠져나감 
            }
            Monitor.Exit(_obj);  
        }
    }

    //무한 대기 상태로 DeadLock이 발생함.
    static void Thread_2()
    {
        for (int i = 0; i < 100000; i++)
        {
            Monitor.Enter(_obj);
            number--;
            Monitor.Exit(_obj);
        } 
    }

 

        위와 같이 return을 해버리면 Exit로 빠져나오지 못하고 계속 잠겨있는 상태가 된다.

       그러면 Thread 2는 열리기만을 기다리는 상태로 무한 대기에 빠져 DeadLock이 발생한 것이다. 

 

 

 

   💡  해결 방법? 

            ▶  try finally로 해결할 수 있다. 

                  finally를 통해 해당 구문을 반드시 한번은 수행하기 때문이다. 

try
{
        Monitor.Enter(_lock);
        return;
}
finally
{
        Monitor.Exit(_lock);
}

       

         하지만 코드가 길어지고 복잡해 질 가능성이 있다..

        가장 좋은 방법은 lock을 사용하는 것이다. 

 

 

 

 

 📌  lock 이란? 

           크리티컬 섹션을 하나의 스레드만 실행할 수 있도록 해주는 것

 

 

            다음과 같이 구현하면 된다. 

static void Thread_1()
    {
        for (int i = 0; i < 100000; i++)
        {
            lock(_obj)
            {
                number++;
            }
        }
    }
static void Thread_2()
{
    for (int i = 0; i < 100000; i++)
    {
        lock(_obj)
        {
            number--;
        }
    } 
}

 

       안전한 스레드를 만들기 위해 Lock 방법을 사용하는 것을 가장 추천한다. 

 

 

 

 

출처 : https://www.inflearn.com/course/유니티-mmorpg-개발-part4

728x90
반응형