ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lock 기초
    게임서버/멀티쓰레드 프로그래밍 2022. 3. 10. 19:42

    1. lock

    Interlocked를 통해서 원자성을 지키는 것을 저번에 알아보았다.

    그러나 Interlocked의 단점은 정수형만 쓸 수 있다는 점이다.

    굳이 Interlocked를 쓰지 않아도 원자성을 보장하는 방법이 있는데 그것은 Monitor.Enter와 Monitor.Exit이다

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ServerCore
    {
        class Program
        {
            static int number = 0;
            static object _obj = new object();
            
            static void Thread_1()
            {
                for (int i = 0; i < 100000; i++)
                {
                    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);
            }
        }
    }

    문을 잠구고 혼자 사용하는 행위를 상호배제(Mutual Exclusive)라고 하며 이 안에서는 싱글 쓰레드처럼 프로그래밍을 하여도 된다.

     

    하지만 Monitor안의 코드가 길어져서 아래의 코드와 같이 문을 여는 행위를 실수로 넘겨버릴 수 있다.

                for (int i = 0; i < 100000; i++)
                {
                    Monitor.Enter(_obj); // 문을 잠구는 행위
    
                    number++;
                    
                    return;
    
                    Monitor.Exit(_obj); // 문을 여는 행위
                }

     

    return키워드로 인해서 문을 여는 행위가 의도치 않게 스킵되어버렸다. 

    문을 열지 못해서 더 이상 열쇠를 사용하지 못하는 경우를 죽은열쇠(Deadlock)라고 표현한다.

     

    데드락을 막기위한 방법을 알아보자.

     

    방법1. return위에 문을여는 코드삽입

            static void Thread_1()
            {
                for (int i = 0; i < 100000; i++)
                {
                    Monitor.Enter(_obj); // 문을 잠구는 행위
    
                    number++;
                    
                    if(number == 10000)
                    {
                        Monitor.Exit(_obj); // 문을 여는 행위
                        return;
                    }
                    Monitor.Exit(_obj); // 문을 여는 행위
                }            
            }

    첫번째 방법은 모든 return위에다가 Monitor.Exit()를 넣어주는 방법이다.

    하지만 이렇게 하면 코드가 더러워지고 일일이 다 넣어줘야하는 번거로움이 있어서 좋은 방법은 아니다.

     

    방법2. try ~ finally 사용

            static void Thread_1()
            {
                for (int i = 0; i < 100000; i++)
                {
                    try
                    {
                        Monitor.Enter(_obj); // 문을 잠구는 행위
                        number++;
                    }
                    finally
                    {
                        Monitor.Exit(_obj); // 문을 여는 행위
                    }                
                }            
            }

    try~finally를 사용하면 반드시 문을 여는 행위가 한번은 실행된다.

     

    방법3. lock키워드사용

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

    lock 키워드에는 내부적으로 Monitor.Enter(), Monitor.Exit()가 들어있다.

    '게임서버 > 멀티쓰레드 프로그래밍' 카테고리의 다른 글

    Lock 구현 이론  (0) 2022.03.11
    데드락  (0) 2022.03.10
    Interlocked  (0) 2022.03.10
    메모리 베리어  (0) 2022.03.10
    캐시 이론  (0) 2022.03.09
Designed by Tistory.