1. SpinLock : 존버메타
 - Lock풀릴때까지 무한정 기다린다.

느낌적으로 짜보자.

   class SpinLock
    {
        volatile bool _locked = false;

        public void Acquire()
        {
            // 잠금이 풀릴때 까지 대기
            while (_locked)
            {
            }

            // 획득
            _locked = true;
        }

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

    class Program
    {
        const int MAX = 100000;
        static int _num = 0;
        static SpinLock _lock = new SpinLock();

        static void Thread_1()
        {
            for (int i = 0; i < MAX; ++i)
            {
                _lock.Acquire(); // 획득
                ++_num;
                _lock.Release(); // 해제
            }
        }

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

결과
 - 어림없다.
 - Acquire 함수가 Atomic이 보장이 안되기때문.

        public void Acquire()
        {
            // 두개 Thread가 동시에 접근할 경우 _locked는 아직 false이므로
            while (_locked)
            {
            }

            // 두 Thread 모두 _locked를 획득하게 된다.
            _locked = true;
        }

해결법 1.
Interlocked.Exchange 사용
 - Atomic을 보장하는 함수
 - 값을 ref 변수에 Atomic을 보장하여 넣어준다.
 - 반환값은 값을 넣기전에 값

    class SpinLock
    {
        volatile int _locked = 0;

        public void Acquire()
        {
            while (true)
            {
                // Interlocked으로 _locked에 1을 대입한다.
                // 반환값이 0이면 이전에 사용하고 있던 Thread가 없는 것이고,
                // 반환값이 1이면 이전에 Thread가 사용하고 있고 아직 Release안된 상태.
                int original = Interlocked.Exchange(ref _locked, 1);
                if (original == 0)
                    break;
            }
        }

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

해결법 2.
위 방법도 괜찮지만 뭔가 직관적이지 않음
 - 무조건 Lock을 걸어주고, Lock걸기 이전에 Lock이 안걸려있는 경우에 Lock이 걸린다 : 복잡..
 - Lock이 걸려있지 않으면 Lock을 걸어준다 : Good
 - 간단한 코드로 써보면

if(_locked == 0)
    _locked = 1;

하지만 이렇게하면 Atomic하지 않기 때문에 Interlocked에서 제공되는 함수가 있다.
Interlocked.CompareExchange(ref _locked, 넣을값, 비교값)
 - _locked와 '비교값'을 비교하여 같으면 '넣을값'을 _locked에 넣어준다.
 - Interlocked.Exchange와 똑같이 original값을 반환한다.

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

변수를 사용하여 좀 더 명시적으로 바꾸면

int expected = 0; // 예상한 값
int desired = 1; // 예상한 값이 맞으면 넣을 값
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
    break;

전체코드

    class SpinLock
    {
        volatile int _locked = 0;

        public void Acquire()
        {
            while (true)
            {
                int expected = 0; // 예상한 값
                int desired = 1; // 예상한 값이 맞으면 넣을 값

                // _locked가 0이면 1을 넣어주고, 0(original == expected)을 반환다.
                // original이 0이었다는건 이전에 사용하고있지 않은 상태이기 때문에 사용권을 획득한다.
                if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
    				break;
            }
        }

        public void Release()
        {
            // 이미 Acquire 단계에서 Atomic이 보장된 상황이기때문에 별도 Interlocked없이 해제해도 된다.
            _locked = 0;
        }
    }

'프로그래밍 > 네트워크' 카테고리의 다른 글

Spin Lock 개선  (0) 2021.09.02
Lock  (0) 2021.08.29
Race Condition  (0) 2021.08.29
Cache  (0) 2021.08.27
Thread의 생성  (0) 2021.08.27

+ Recent posts