※ 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을 써준 것
}