C++ SpinLock

정은성·2023년 3월 16일
0
post-thumbnail

※ Rookiss님의 [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 강의를 보고 정리한 글입니다.

SpinLock: Lock에서 다른 쓰레드가 사용중이라 못 들어갈 때 쓰레드가 사용을 끝마칠때까지 기다리는 방법

void Add() {
for (int32 i = 0; i < 10'0000; i++)
{
	lock_guard<SpinLock> guard(spinLock);
	sum++;
}

}
void Sub() {
for (int32 i = 0; i < 10'0000; i++)
{
	lock_guard<SpinLock> guard(spinLock);
	sum--;
}	
}
int main()
{
std::thread t1(Add);
std::thread t2(Sub);

t1.join();
t2.join();

cout << sum << endl;
}

SpinLock은 짧게 생각하면 이렇게 만들어볼 수 있을 것이다.

class SpinLock {
public:
	void lock() { 
		// 대문자 X, lock_Guard에서 사용할 거라 시그니쳐를 맞춰줘야함
			
		while(_locked)
		{

		}
			
		_locked = true;
	}

	void unlock() {
		_locked = false;
	}

private:
		bool _locked = false;
};

하지만 이렇게 만들 경우 0이 아닌 숫자가 나오게 된다.. ← 의도대로 실행되지않은 것!!

왜 그렇지??

컴파일러의 최적화 때문이 아닌가 생각해 볼 수 있다.

컴파일러 최적화?
→ 컴파일러에서 출력되는 실행 프로그램의 효율성을 최적화하는 과정

volatile키워드를 붙인다면?

...
volatile bool _locked = false;
// volatile: C++에선최적화에 쓰지말아달라는 뜻
// C#에선 메모리 베리어, 가시성 등등....

그래도 해결되지않는다.

그럼 최적화가 문제가 아니라는 것!

문제는 동시에 while문에 들어가는 상황이다.

지금은 _locked == false_locked = true 두 가지 행동을 해야한다.

이것은 우리가 말한 원자성에 어긋난다.

원자성 보장

_locked변수를 atomic으로 선언한다.

atomic<bool> _locked;

compare_exchange_strong(expected, desired): 비교와 대입을 동시에 해주는 함수

⇒ _locked가 expected라면?? desire값으로 _locked에 대입, expected에 _locked 값 대입

풀어쓰면

if(_locked == expected)
{
	expected = _locked;
	_locked = desireed;
	return true;
}
else
{
	expected = _locked;
	return false;
}

이런 코드가 된다.

적용

void lock() {
		
		bool expected = false;
		bool desire = true;
	
		while (_locked.compare_exchange_strong(expected, desire) == false) {

			expected = false; //expected값은 항상 바뀜 다시 초기화해주기
		}

		_locked = true;

	}

	void unlock() {
		_locked.store(false); 
	// 그냥 _locked = false 로하면 _locked가 atomic변수인지 잘 티나지않으므로 store을 써준 것
	}

0개의 댓글