C#/C# 서버

[C#/서버] SpinLock

진서박 2023. 8. 18. 21:57
반응형

인프런 - Rookies님의 강의

https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4

 

[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의

네트워크/멀티쓰레드/운영체제 등 핵심 전공 지식을 공부하고 게임 서버를 바닥부터 만들어보면서 MMORPG 기술을 학습하는 강의입니다., MMORPG 개발에 필요한 모든 기술, C# + Unity로 Step By Step! 🕹

www.inflearn.com

 

 

 

쉽게 말해서 락을 획득할 때 까지 락의 상태를 계속해서 확인하는 것

 

다음 코드를 보면 이제까지의 싱글쓰레드의 사고방식으로는 아무 문제가 없어 보인다. 실행 결과는 0이 나오겠지?

class SpinLock
    {
        volatile bool _locked = false;

        public void Acquire()
        {
            while (_locked)
            {
                // 잠금이 풀리기를 기다림

            }

            // 내꺼
            _locked = true;
        }

        public void Release()
        {
            _locked = false;


        }
    }

    internal class Program
    {
        static int _num = 0;
        static SpinLock _lock = new SpinLock();

        static void Thread_1()
        {
            for(int i = 0; i < 100000; i++)
            {
                _lock.Acquire();
                _num++;
                _lock.Release();
            }
        }

        static void Thread_2()
        {
            for (int i = 0; i < 100000; i++)
            {
                _lock.Acquire();
                _num--;
                _lock.Release();
            }
        }

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

띠용~

이상하다 이상해. 

문제는 멀티쓰레드 환경에서의 "원자성" 을 고려하지 않았다.

다음 코드에서 문제가 발생한다.

        public void Acquire()
        {
            while (_locked)
            {
                // 잠금이 풀리기를 기다림

            }

            // 찜!
            _locked = true;
        }

잠금이 풀리기를 기다리는 부분과 내꺼라고 찜하는 부분이 따로 나뉘어져 있다. 이렇게 되면 두개의 Task가 거의 동시에 _lock이 풀리고 찜해버리는(잠궈버리는) 상황이 발생할 수 있다. 그렇기 때문에 원자성을 고려하여 [잠금이 풀리기를 기다리다가 -> 잠금이 풀리면 -> 찜] 동작 한번에 동작할 수 있도록 만들어야 한다.

 

C# 에서는 Interlocked로 구현할 수 있는데 두가지 종류가 있다.

1. Interlocked.Exchange(ref int location1, int value) 

- value값을 location1에 대입하는 기능을 하고, 반환값으로 value를 대입하기 전의 original 값을 반환한다

- 일반적으로 잘 쓰이지 않는다

 

2. Interlocked.CompareExchange(ref int location1, int value, int comparand)

- CAS : Compare-And-Swap 방식의 함수로 자주 사용한다

- value와 comparand를 비교해서 같으면 value를 location1에 대입한다

- 마찬가지로 반환값으로 value를 대입하기 전의 original 값을 반환한다

 

간단한 구현은 다음과 같다

        public void Acquire()
        {
            while (true)
            {
                // Exchange : location1에 value를 대입
                // 반환값 : value를 대입하기 전의 original 값
                // 1을 대입했을 때, 원래 값이 0이었다면(잠겨있지 않았다면) 찜할 수 있음 => 반복문 빠져나옴
                // 1을 대입했을 때, 원래 값이 1이었다면(잠겨있었다면) 찜할 수 없음 => 반복문 못빠져나옴
                int original = Interlocked.Exchange(ref _locked, 1);
                if (original == 0)
                    break;

                // CAS : Compare-And-Swap
                // value(desired)와 comparand(expected)를 비교해서 같으면 value를 location1에 대입
                // 반환값 : value를 대입하기 전의 original 값
                int expected = 0;   // 예상한 값
                int desired = 1;    // 원하는 값
                if(Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
                    break;
            }
        }

 

주의할 점

lock이 곧 사용가능해 질 경우 context switching을 줄여 CPU의 부담을 덜어주지만, 쓰레드의 작업이 길어져 lock이 안풀릴 경우 CPU 시간을 오히려 많이 소모할 가능성이 있다.

반응형

'C# > C# 서버' 카테고리의 다른 글

[C#/서버] ReaderWriterLock  (0) 2023.08.22
[C#/서버] AutoResetEvent / ManualResetEvent  (0) 2023.08.20
[C#/서버] 메모리 배리어  (0) 2023.08.16
[C#/서버] 캐시 이론  (0) 2023.08.16
[C#/서버] 쓰레드 풀 (ThreadPool)  (0) 2023.08.16