using System;
using System.Threading;
using System.Threading.Tasks;

namespace ServerCore
{
    class SpinLock
    {
        // _locked == true -> 잠김상태
        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 Thread1()
        {
            for (int i = 0; i < 100000; i++)
            {
                _lock.Acquire();
                _num++;
                _lock.Release();
            }
        }

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

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread1);
            Task t2 = new Task(Thread2);
            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(_num);
        }
    }
}

SpinLock의 구현
하지만 정상작동하지 않음
 

public void Acquire()
        {
            // 잠김상태이면 무한루프로 풀리기를 기다린다.
            while(_locked)
            {
                
            }
            // 잠김이 풀렸으니 내가 사용
            _locked = true;
        }

잠김상태를 확인하고 잠겨있지 않았다면 내가 잠그는 행위가 원자적으로 이루어지지 않았기 때문에 발생하는 문제
 

class SpinLock
    {
        // _locked == true -> 잠김상태
        volatile int _locked = 0;
        public void Acquire()
        {
            while (true)
            {
                int original = Interlocked.Exchange(ref _locked, 1);
                if (original == 0) break;
            }
        }
        public void Release()
        {
            _locked = 0;
        }
    }

Interlocked.Exchange를 사용하여 문제 해결 (_locked 변수는 bool 타입에서 int 타입으로 변경하여 사용)
original = Interlocked.Exchange(ref _locked, 1)은 _locked 변수에 1을 대입하고 기존 _locked 변수를 original에 return 해준다.
즉, 기존 _locked값이 0이었다면 잠김상태가 아니란 뜻이니 1을 대입하여 잠가주고 break를 통해 빠져나오는 것이다.
 

{
    int original = _locked;
    _locked = 1;
    if (original == 0) break;
}

Acquire()의 작동방식 (Interlocked.Exchange)
 

public void Acquire()
        {
            while (true)
            {
                int original = Interlocked.CompareExchange(ref _locked, 1, 0);
                if (original == 0) break;
            }
        }

Interlocked.CompareExchange를 사용하여 문제를 해결할 수도 있다.
_locked과 0이 같아면 _locked에 1을 대입해주는 방식
 

{
    if (_locked == 0) _locked = 1;
}

Interlocked.CompareExchange의 작동방식 (CAS (Compare - And - Swap) 계열)
 

Thread.Sleep(1); // 무조건 휴식 => 1ms
Thread.Sleep(0); // 조건부 양보 => 나보다 우선순위가 낮은 애들한테는 양보 불가 => 우선순위가 나보다 같거나 높은 쓰레드가 없으면 다시 본인한테
Thread.Yield(); // 관대한 양보 => 관대하게 양보, 지금 실행이 가능한 쓰레드가 있으면 실행 => 실행 가능한 쓰레드가 없으면 남은시간 소진

일정 시간 뒤 다시시도를 구현하기 위해 셋중 하나를 선택하여 사용
 

class Program
    {
        static SpinLock _lock = new SpinLock();
        static void Main(string[] args)
        {
            bool lockTaken = false;

            try
            {
                _lock.Enter(ref lockTaken);
            }
            finally
            {
                if (lockTaken) _lock.Exit();
            }
        }
    }

SpinLock은 이미 구현이 되어있어 그대로 사용해도 된다.
내부적으로는 계속 시도를 하지만 계속 Lock 상태라면 Yield를 가끔 주어 양보를 하기도 한다.

'네트워크' 카테고리의 다른 글

ReaderWriterLock  (0) 2023.08.04
AutoResetEvent  (0) 2023.08.03
DeadLock  (0) 2023.08.01
Lock 기초  (0) 2023.07.31
Interlocked  (0) 2023.07.30

+ Recent posts