본문 바로가기
프로그래밍/Game Dev

[C#] SpinLock

by YuminK 2023. 8. 26.

키를 가진 사람은 하나이고 화장실에 들어가서 문을 잠근다. 볼일을 보고 나가면서 키를 두고 나간다. 

 

다음 코드는 SpinLock이 제대로 동작하지 않는 코드이다. 

class SpinLock
{
    volatile bool _locked = false;


    public void Acquire()
    {
        while (_locked)
        {
            // 잠김이 풀릴 때까지 기다린다. 
        }

        _locked = true;
    }

    public void Release()
    {
        _locked = false;
    }
}

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

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


    static void Thread_2()
    {
        for (int i = 0; i < 10000; ++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 = " + _num);
    }
}

 

원인은 여러 쓰레드에서 동시에 키(_locked)에 접근할 수 있기 때문이다.

즉, 키를 얻는 행위와 다른 사람이 들어올 수 없도록 잠그는 행위가 원자적으로 이뤄져야 한다. 

 

이러한 처리를 위해 Interlocked 함수를 이용한다. 

SpinLock 클래스를 수정한다. 

 

class SpinLock
{
    volatile int _locked = 0;

    public void Acquire()
    {
        while (true)
        {
            // Version 1
            // 하나의 쓰레드에서 처리하기 때문에 original 값을 비교해서 사용해도 된다. 
            //int original = Interlocked.Exchange(ref _locked, 1);
            //if (original == 0)
            //    break;

            // Version 2

            // locked와 0의 값이 같은 경우 1로 교체하고 original 반환 

           int original = Interlocked.CompareExchange(ref _locked, 1, 0);
            if (original == 0)
                break;

 

            //Thread.Sleep(1); // 무조건 휴식 ==> 무조건 1ms 정도 쉬고 싶어요.
            //Thread.Sleep(0); // 조건부 양보 => 우선순위가 같거나 높은 스레드가 없는 경우 자신이 처리, 우선 순위가 낮은 스레드에게 양보X

            Thread.Yield(); // 관대한 양보 => 실행 가능한 쓰레드가 없는 경우 자신에게 시간 부여



            //C++ Style
            //int expected = 0;
            //int desired = 1;
            //if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
            //    break;

            //{  
            //    // 싱글 스레드라면 이런 느낌
            //    int original = _locked;
            //    _locked = 1;
            //    if (original == 0)
            //        break;
            //}
        }
    }

    public void Release()
    {
        _locked = 0;
    }
}

 

Interlocked 함수는 original 값을 받고 원하는 값을 연산해주는 처리를 '원자적'으로 지원한다.

original 값이 0이라면 키를 얻었다는 의미이므로 loop를 나가면 된다. 

정상적으로 num의 값이 0으로 나온다. 

'프로그래밍 > Game Dev' 카테고리의 다른 글

[C#] Thread Local Storage  (0) 2023.08.28
[C#] AutoResetEvent  (0) 2023.08.26
[C#] Interlocked  (0) 2023.08.26
[C#] MessagePack  (0) 2023.08.26
[C#] 메모리 베리어  (0) 2023.08.26

댓글