[Server Programming/cpp] SpinLock
앞에서 Lock의 기본적인 사용에 대해 알아 보았다. 이번에는 SpinLock을 직접 구현해보고 특성을 파악해보려 한다.
SpinLock
void Add() {
for (int32 i = 0; i < 10'000; i++)
{
lock_guard<mutex> guard(_mutex);
sum++;
}
}
void Sub() {
for (int32 i = 0; i < 10'000; i++)
{
lock_guard<mutex> guard(_mutex);
sum--;
}
}
위와 같이 1만번을 더하고 빼는 두 함수를 다중 쓰레드로 실행하려 한다. 현재 mutex를 통해서 락을 하고있고, 실행해보면 정상적인 결괏값을 얻을 수 있다.
SpinLock으로 구현 1
class SpinLock {
public:
void lock() {
while (_locked) {
}
_locked = true;
}
void unlock() {
_locked = false;
}
private:
bool _locked = false;
};
...
void Add() {
for (int32 i = 0; i < 10'000; i++)
{
lock_guard<SpinLock> guard(spinLock);
sum++;
}
}
...
으로 락을 직접 생성한 SpinLock 클래스로 대체 해보았다. 그 결과는 어땠을까?
-139 ... // 생각한대로 나오지 않는다. 어디가 문제일까?
volatile
빌드 구성에 Debug와 Release모드가 있는데, 이 둘의 차이는 컴파일 시 컴파일러가 최적화를 해주냐 안해주냐에 있다.
그리고 volatile 접두사는 한 변수를 컴파일 시 어떤 모드에서든 최적화하지 말라는 의미가 된다 (c++에서)
예제를 보면 이해가 쉽다.
최적화가 되지 않은 경우, Debug모드일때
00007FF73DC0747D nop
int32 a;
a = 0;
00007FF73DC0747E mov dword ptr [a],0
a = 1;
00007FF73DC07485 mov dword ptr [a],1
a = 2;
00007FF73DC0748C mov dword ptr [a],2
a = 3;
00007FF73DC07493 mov dword ptr [a],3
a = 4;
00007FF73DC0749A mov dword ptr [a],4
a = 5;
00007FF73DC074A1 mov dword ptr [a],5
cout << a << endl;
모든 코드에 대해 한줄 한줄 어셈블리어로 변환되어 실행된다.
이는 volatile int32이라고 선언했을때와 같은 효과를 갖는다.
컴파일러에 의해 최적화가 된 경우
int32 a;
a = 0;
a = 1;
a = 2;
a = 3;
a = 4;
a = 5;
cout << a << endl;
00007FF6E3731077 mov edx,5
Release모드로 빌드한다면 어셈블리어로 위와 같은 결과를 볼 수 있다.
어차피 a=5로 덮어지기에 전부 무시하고 a=5만 어셈블리어로 실행되는 모습.
여기서 volatile을 int32 a에 적용하면 첫번째 예시처럼 실행될 것이다.
volatile을 설명한 이유
위의 lock 예제에서 루프문에서 또한 최적화가 일어나면 검사를 일일히 안하기 때문이다.
왜 그럴까? cpu는 다른 스레드의 존재를 인식하지 못하기 때문에 이미 변수가 false인 상황에서 반복문의 조건을 따질 이유가 없기 때문이다.
void lock() {
while (_locked) {
// 어차피 _locked가 false이니 검사 계속 안할래~
}
_locked = true;
}
하지만 멀티스레드 환경에서는 다른 스레드에 의해 변수가 언제든지 변할 수 있기에 우리는 무한 루프를 돌렸던 것이다.
따라서 volatile을 할당하면 컴파일러의 의사와 상관없이 최적화 시키지말고 있는 그대로 해석해달라는 의미가 된다.
volatile bool _locked = false;
atomic을 사용하자
하지만 실전에선
volatile bool _locked = false;
가 아닌 atomic으로 locked를 감싼다.
atomic<bool> locked = false;
atomic에서 volatile 기능까지 포함하기 때문.
출처
인프런 Rookiss, "[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버"