본문 바로가기
개인 스터디

스핀락

by 태운콩즙 2024. 10. 16.
728x90
반응형

스핀락은 폴링 방식의 동기화 처리를 하는 방식이다

마이크로 프로세서 에서 내부 인터럽트를 사용하지 않고 값의 state 가 바뀌는지 while(1) 을 이용하여 확인 하는 것 과 같다

예를 들어 num 이라는 데이터를 첫 번째 스레드 에선 +1씩 계속 증가 시키고 두 번째 스레드 에선 -1씩 계속 감소 시키는 행위를 각각 10만번씩 하면 직관적으로는 당연히 프로그램이 뻗지 않는 이상 당연히 0이 나올 것 이다 하지만 자원 접근에 대한 동기화가 이루어 지지 않으면 한 쪽에서 값을 변경 시켜도 다른 쪽에선 이전에 가지고 있던 값을 최신이라고 저장 하고 있다가 값을 변경시키는 현상이 발생 하므로 의도했던 데이터 갱신이 이루어지지 않는다

이를 해결 하기 위해 첫 번째 폴링 방식의 스핀 락을 구현 할 때 발생할 수 있는 문제점과 이를 해결하기 위한 C# 에서 제공되는 Interlock 의 클래스를 이용한 예를 살펴보자

class SpinLock
	{
		volatile int locked = 0;
		public void Acquitre()
		{
			while(true)
			{
				if(lock == 1)
				break;
			}
			
			locked = 1;
		}
		public void Release()
		{
			locked = 0;
		}
	}
	class Program
	{
		static int num = 0;
		static SpinLock _lock = new SpintLock();
		
		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.WaiAll(t1,t2);
			
			Console.WriteLine(num); // 0인지 확인
		}
	}

이 코드를 보면 마치 둘 중에 누군가 먼저 도착하는 스레드가 locked을 잡은 다음 값을 1로 바꾸고 다음 스레드가 locked 이 0인 동안 대기하고 있다가 locked이 1로 풀리면 사용하면 될 거 같지만 위 코드에서 문제가 되는 부분은 while문 내부의 코드이다

위 코드는 두 스레드가 동시에 locked에 접근하여 0인 state를 갖는 것이다 이렇게 되면 두 스레드 모두 누군가 사용 하고 있는 것으로 생각해 교착 상태에 빠지게 된다 이 동시성 제어를 해결하기 위해 Interlocked 클래스에서 제공되는 Exchage를 사용해 해결한다

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

Exchange 를 살펴보면

첫 번째 매개변수에는 우리가 자물쇠라고 보통 표현하는 잠금 장치 값을 설정해주고 두 번째 매개변수에는 우리가 바꿀 값을 사용해준다.

가장 중요 한 건 반환 값 인데 여기서 반환 값은 우리가 1로 바꾸기 전의 locked에 있는 값이다

따라서 original_value가 0으로 반환 되었다 는 건 다른 스레드 가 locked을 1로 바꾸기 전 현재 진입한 스레드 가 자물쇠를 얻을 수 있다는 말이 된다 그래서 만약 어떤 스레드가 저 while 루프로 진입 하면

값이 1로 바뀌고 기존 값이 0이면 자기가 바꾸고 while 루프를 나가게된다

그럼 두 번째 온 스레드가 첫 진입한 스레드가 작업을 마치지 않았다면 original 값은 1로 바뀌어 있을 테고 첫 진입한 스레드가 Release() 함수를 실행하기 전까지 while 문에서 무한정 대기 할 것이다

CompareExchange

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 desire, expected) == expected)
						     break;
        }
        public void Release()
        {
            locked = 0;
        }
      }

CompareExchange 를 보면

첫 번째 파라미터는 자물쇠의 역할이고

두 번째 파라미터는 바꾸고자 하는 값

세 번째 파라미터는 locked과 비교하는 값이다

따라서 만약 locked과 expcted 값이 같다면 desire로 값이 바뀌고

반환 값은 Exchange와 똑같이 원래의 값을 반환한다

728x90
반응형

'개인 스터디' 카테고리의 다른 글

비동기 TCP 서버  (0) 2024.10.17
Monitor  (0) 2024.10.16
C# Thread(스레드) 주요 속성과 메서드  (0) 2024.10.15
무명 메서드 (Anonymous Method)  (0) 2024.10.14
TCP 서버  (0) 2024.10.11