KoreanFoodie's Study
[C++ 게임 서버] 1-7. SpinLock 개념과 구현 본문

[C++ 게임 서버] 1-7. SpinLock
핵심 :
1. SpinLock 은 Lock 을 잡기 위해 '무작정 기다리는' 방식이다.
2. SpinLock 은 atomic 변수와 lock, unlock 함수로 쉽게 만들 수 있다. compare_exchange_strong 함수를 기억하자.
3. SpinLock 은 불필요한 CPU clock 을 낭비하지만, 때로는 컨텍스트 스위칭에 드는 것보다 비용이 저렴할 수도 있다.
이제 본격적으로 SpinLock 을 구현해 보자.
SpinLock 은 간단하다. 그냥 Lock 을 잡을 때까지 무작정 While 문을 돌면서 기다리도록 만들 것이다 🤣
간단하게 생각하면 아래와 같이 구현하겠지만...
int sum = 0;
class SpinLock
{
private:
	bool _lock = false;
public:
	void lock()
	{
		while (_lock)
		{
		}
		_lock = true;
	}
	void unlock()
	{
		_lock = false;
	}
};
SpinLock spinLock;
void Add()
{
	for (int i = 0; i < 1000; ++i)
	{
		spinLock.lock();
		++sum;
		
		spinLock.unlock();
	}
}
void Sub()
{
	for (int i = 0; i < 1000; ++i)
	{
		spinLock.lock();
		--sum;
		spinLock.unlock();
	}
}
int main()
{
	thread t1(Add);
	thread t2(Sub);
	t1.join();
	t2.join();
	cout << "sum : " << sum << endl;
}
위 코드는 제대로 동작하지 않는다.

그 이유는 크게 2 가지가 있다.
일단, SpinLock 클래스의 _locked 는 멀티쓰레드 환경을 고려하지 않고 선언한 녀석이다.
무슨 말인고 하니, 다음과 같은 상황이 있다고 가정해 보자.
int a = 0;
a = 1;
a = 2;
a = 3;
컴파일러는 이 코드를 어셈블리어로 만들 때, 매우 '똑똑한' 최적화를 한다. 즉, a 의 최종값은 어차피 3이니, 굳이 0, 1, 2 값을 대입하지 않고 바로 3 을 넣는 어셈블리어만 생성하는 것이다.
'모종의' 이유로, 컴파일러가 이러한 최적화를 하지 않도록 하려면, 변수를 선언할 때 volatile 을 붙여주면 된다!
// 이제 대입할 때마다 그에 맞는 어셈블리어가 생성된다.
volatile int a = 0;
아, 물론 atomic 을 사용해도 된다. atomic 은 volatile 을 포함하는 녀석이다 😉
이제 SpinLock 을 아래와 같이 제대로 구현할 것이다... LockGuard 도 사용해서 😂
참고로, 우리가 만드는 클래스에 lock 과 unlock 함수가 있으면 std::lock_guard 를 만들때 넣어서 사용할 수 있다!
class SpinLock
{
private:
	atomic<bool> _locked = false;
public:
	void lock()
	{
		bool expected = false;
		bool desired = true;
		while (_locked.compare_exchange_strong(expected, desired) == false)
		{
			expected = false;
		}
	}
	void unlock()
	{
		_locked.store(false);
	}
};
SpinLock spinLock;
void Add()
{
	for (int i = 0; i < 10'000; ++i)
	{
		lock_guard<SpinLock> lockGuard(spinLock);
		++sum;
	}
}
위 코드를 보면, compare_exchange_strong 이라는 녀석이 나오는데, 아래 부분의 의미는 다음과 같다 :
if (_locked == expected)
{
    expected = _locked;
    _locked = desired;
    return true;
}
else
{
    expected = _locked;
    return false;
}
핵심은 _locked 가 expected 와 같은지를 판단하는 것이다. 그런데 expected 는 항상 _locked 의 값을 가지게 되는데...

설명을 잘 보면 expected 변수를 참조하여 사용하고 있다. 따라서, Lock 을 실제로 잡는데 성공했을 때에는, expected 값을 다시 false 로 바꾸어 주어야 한다! 😊
'Game Dev > Game Server' 카테고리의 다른 글
| [C++ 게임 서버] 1-9. 이벤트 구현 (+ Handle) (0) | 2023.07.14 | 
|---|---|
| [C++ 게임 서버] 1-8. Sleep 개념과 구현 (0) | 2023.07.12 | 
| [C++ 게임 서버] 1-6. Lock 구현 이론 (0) | 2023.07.11 | 
| [C++ 게임 서버] 1-5. DeadLock 의 개념과 기초 (+ std::lock) (0) | 2023.07.10 | 
| [C++ 게임 서버] 1-4. Lock 기초 (lock_guard, unique_lock) (0) | 2023.07.10 |