(C# : Server) (Lock 구현) SpinLock

Posted by : at

Category : Charp-Server


class SpinLock
{
    volatile bool _locked = false;

    public void Acquire()
    {
        while(_locked)
        {
            // 잠김이 풀리기를 대기
        }

        _locked = true;
    }

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

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

너무 쉬운데???
하지만… 동작해 보면 _num이 0이 안나온다

왜지?

class SpinLock
{
    volatile bool _locked = false;

    public void Acquire()
    {
        // 두 쓰레드가 거의 동시에 while문에 도착
        while(_locked)
        {
            
        }

        // 그럴경우 두 쓰레드가 모두 락을 획득하게 된다.
        _locked = true;

        /*
            근본적으로 이런 문제가 일어난 원인은
            락을 확인하는 while과 락을 잠그는 _locked = true를
            한 동작으로 해야지 따로따로 해서 발생한 문제이다
            어려운 말로는 아토믹하게 처리해보자
        */
    }

    public void Release()
    {
        _locked = false;
    }
}
class SpinLock
{
    volatile int _locked = 0;

    public void Acquire()
    {
        while(true)
        {
            int original = Interlocked.Exchange(ref _locked, 1)
            if(original == 0)
            {
                // _locked를 1로 바꿔 주세요
                // 그런데 original이 0이여야 break를 타게 해주세요
                break;
            }
        }
    }

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

이번엔 다른 버전으로 개발해보자

class SpinLock
{
    volatile int _locked = 0;

    public void Acquire()
    {
        while(true)
        {
            // CAS(Comapare And Swap)
            int original = Interlocked.CompareExchange(ref _locked, 1, 0);
            if(original == 0)
                break;
            // _locked이 0일 경우 1을 넣어주세요
            // 성공 실패여보는 original을 통해 확인
        }
    }

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

이렇게 구현할 경우 조금 헷갈리니 변수를 써주는 방향으로 구현을 많이 한다.

class SpinLock
{
    volatile int _locked = 0;

    public void Acquire()
    {
        while(true)
        {
            // CAS(Comapare And Swap)
            int expected = 0;
            int desire = 1;
            int original = Interlocked.CompareExchange(ref _locked, desire, expected);
            if(original == 0)
                break;
        }
    }

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

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links