스핀 락
스핀락은 폴링 방식의 동기화 처리를 하는 방식이다.
마이크로 프로세서 에서 내부 인터럽트를 사용하지 않고 값의 state 가 바뀌는지
while(1) 을 이용하여 확인 하는것과 같다.
한 가지 예를 보자
num 이라는 데이터를
첫번째 스레드에선 +1 씩 계속 증가시키고
두번째 스레드에선 -1 씩 계속 감소시키는 행위를 각각 10만번 씩 하면
직관적으로는 당연히 프로그램이 뻗지 않는이상 당연히 0이 나올 것이다.
하지만 이전 포스팅에서 자원 접근에 대한 동기화가 이루어 지지 않으면 한 쪽에서 값을 변경 시켜도
다른 쪽에선 이전에 가지고 있던값을 최신이라고 저장하고 있다가 값을 변경시키는 현상이 발생하므로
의도했던 데이터 갱신이 이루어지지 않는다.
이를 해결하기 위해 첫번째 폴링 방식의 스핀락을 구현할때 발생할 수 있는 문제점과
이를 해결하기 위한 C#에서 제공되는 Interlock의 클래스를 이용한 예를 살펴보자.
class SpinLock
    {
        volatile int locked = 0;
        public void Acquire()
        {
            while(true)
            {
                if (locked == 1)
                    break; 
            }
         
            locked = 1;
        }
        public void Release()
        {
            locked = 0;
        }
    }
    class Program
    {
        static int num = 0;
        static SpinLock _lock = new SpinLock();
        static void Thread_1()
        {
            for(int i=0; i<100000; ++i)
            {
                _lock.Acquire(); // (locked == true);
                num += 1;
                _lock.Release(); // (locked == false) lock 해제
            }
        }
        static void Thread_2()
        {
           for(int i=0; i<100000;++i)
            {
                _lock.Acquire();
                num -= 1;
                _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); //0인지 확인
        }
    }while(true) 
            { 
                if (locked == 1) 
                    break;  
            } 
          
            locked = 1;
이 코드를 보면 마치 둘 중에 누군가 먼저 도착하는 스레드가 locked을 잡은다음 값을 1로 바꾸고
다음 스레드가 lcoked이 0인동안 (먼저 온 스레드가 사용중) 대기하고 있다가 locked이 1로 풀리면
사용 하면 될거 같다.
하지만, 이 부분에서도 문제가 되는 부분은 저 while 문 내부의 코드이다.
어떠한 상황이 발생 할 수 있는지 살펴보면
둘이 동시에 locked에 접근하여 0인 state를 갖는것이다. 이렇게 되면
첫번째 스레드도, 두번째 스레드도,
"음 누군가 사용 하고 있군 존버해야겠다."
라고 두 스레드 모두 존버하므로 교착상태에 빠지게 된다.
이 동시성 제어를 해결하기 위해
C# 에서 Interlocked 클래스에서 제공되는
Exchage를 사용해서 해결한다.
물론 이전 포스팅의 Increment도 메모리 베리어를 사용하며 증가 감산의 동시성 제어를 할 수 있다.
Interlocked의 Exchange에서 어떻게 작동되는지 코드를 보자.
class SpinLock
    {
        volatile int locked = 0;
        public void Acquire()
        {
            while(true)
            {
                 int original_value = Interlocked.Exchange(ref locked, 1);
                 
                  if (original_value == 0) //만약 원래 값이 0이라면 다른 스레드가 사용하지 않았다고 판단.
                      break;
            }
  
            /*
            while(locked) //폴링 상태로 잠김 대기
             {}
            //lock 획득
             locked = true;*/
        }
        public void Release()
        {
            locked = 0;
        }
    }
    class Program
    {
        static int num = 0;
        static SpinLock _lock = new SpinLock();
        static void Thread_1()
        {
            for(int i=0; i<100000; ++i)
            {
                _lock.Acquire(); // (locked == true);
                num += 1;
                _lock.Release(); // (locked == false) lock 해제
            }
        }
        static void Thread_2()
        {
           for(int i=0; i<100000;++i)
            {
                _lock.Acquire();
                num -= 1;
                _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); //0인지 확인
        }
    }실제 출력을 해보면
교착상태에 빠지지 않고 값이 정상적으로 0이 출력이 된다.

while(true)
            {
                 int original_value = Interlocked.Exchange(ref locked, 1);
                 
                  if (original_value == 0) //만약 원래 값이 0이라면 다른 스레드가 사용하지 않았다고 판단.
                      break;
            }Exchage를 살펴보면
// 요약: 
        //     원자 단위 연산으로 부호 있는 32비트 정수를 지정된 값으로 설정하고 원래 값을 반환합니다. 
        // 
        // 매개 변수: 
        //   location1: 
        //     지정된 값으로 설정할 변수입니다. 
        // 
        //   value: 
        //     location1 매개 변수의 설정 값입니다. 
        // 
        // 반환 값: 
        //     location1의 원래 값입니다. 
        // 
        // 예외: 
        //   T:System.ArgumentNullException: 
        //     location1의 주소는 null 포인터입니다.
이렇게 요약 되어있는것을 확인할 수 있는데
첫번째 매개변수에는 우리가 자물쇠라고 보통표현하는 잠금장치 값 을 설정해주고
두번째 매개변수에는 우리가 바꿀 값을 사용해준다.
가장 중요한건 반환값인데 여기서 반환값은 우리가 1로 바꾸기 전의 locked에 있는 값이다.
따라서 original_value가 0으로 반환 되었다는것은 다른 스레드가 locked을 1로 바꾸기 전,
즉 , 현재 진입한 스레드가 자물쇠를 얻을 수 있다는 말이 된다.
그래서 만약, 어떤 스레드가 최초로 저 while 루프를 진입하면
"값이 1로 바꾸고 싶어, 그리고 기존 값이 0이면 내가 바꾸고 while루프를 나갈꺼야"
로 해석 된다.
그럼 두번째 온 스레드가 첫 진입한 스레드가 작업을 마치지 않았다면 original 값은 1로 바뀌어 있을테고
첫 진입한 스레드가 Release() 함수를 실행하기 전까지 while문에서 무한정 대기할 것이다.
Interlocked의 Exchange와 비슷한 CompareExchage가 있다.
이름만 봐도 뭔가를 비교해서 바꾸는 함수 같다.
실제 그렇다.
 class SpinLock
    {
        volatile int locked = 0;
        public void Acquire()
        {
            while(true)
            {
                //CAS Compare-AND-Swap
                int expected = 0;
                int desire = 1;
                if(Interlocked.CompareExchange(ref locked, desire, expected) == expected)
                    break;
            }
  
        }
        public void Release()
        {
            locked = 0;
        }
    }
CompareExchage를 보면
첫 번째 파라미터는 자물쇠의 역할이고,
두 번째 파라미터는 바꾸고자 하는값,
세 번째 파라미터는 locked과 비교하는 값이다.
따라서, 만약, locked과 expcted값이 같다면 desire로 값으로 바꾸고
반환 값은 Exchage와 똑같이 원래의 값을 반환한다.
좀더 설명을 부연하면,
만약 , locked와 expected값이 0으로 같다 ( 아무도 자물쇠 (locked)를 사용하지 않은 상태이다)
그럼 원래 값 0을 반환시키고 locked을 1로 바꾼다. (다음 순서의 스레드들은 대기하게 된다 1인상태이므로)
'Programing_Language > C#' 카테고리의 다른 글
| [쓰레드 동기화] Interrupt(이벤트) 방식의 AutoResetEvent (0) | 2020.07.21 | 
|---|---|
| [쓰레드 동기화] Thread [Sleep, Yield] , ContextSwitching (0) | 2020.07.21 | 
| [쓰레드 동기화] Monitor 와 lock (IPC) (lock > Monitor 편의성) (0) | 2020.07.18 | 
| [쓰레드 동기화] Interlocked (공유자원과 임계구역) (0) | 2020.07.18 | 
| [쓰레드] TaskCreateOptions.LongRunning (0) | 2020.07.17 | 
 
										
									 
										
									
댓글