🎮
SpinLock
January 06, 2024
Spin Lock
lock이 해제될때까지 바쁜 대기(busy waiting)
스핀락은 유저 레벨에서 무한 while 루프를 돌고 있기 때문에 쓰레드 교체를 하지 않고 계속 CPU 자원(코어를 계속 점유)을 소모하면서 버티는 것 -> 컨텍스트 스위칭이 발생하지 않음.
Spin Lock 구현
ver 1.
class SpinLock
{
private:
volatile bool _locked = false;
public:
void lock()
{
while(_locked)
{
//바쁜 대기
}
_locked = true;
}
void unlock()
{
_locked = false;
}
}
int sum = 0;
SpinLock spinLock;
void Add()
{
for(int i = 0 ; i < 10'0000 ; i++)
{
lock_guard<SpinLock> guard(spinLock);
sum++;
}
}
void Sub()
{
for(int 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;
return 0;
}
실행 결과
-36323
⭐️ TIP C++ 에서의
volatile
키워드 : 최적화를 하지 말아달라.
- 왜 0이 안나오는가 ??
- 락이 걸려있는가를 확인한다.
- 걸려있지 않으면 락을 걸고 통제권을 획득한다.
- 위 경우에서 만약 두 쓰레드가 i. 번을 동시에 확인하여 락이 걸려있지 않음을 확인한다면?
- 통제권을 함께 얻는 경우가 발생함. -> i, ii 가 한 묶음으로 동작하지 않음!
- 즉, 원자성이 보장되지 않음.
- 해결방안 ->
락을 확인한다 + 락을 건다
가 한 묶음으로 동작해야 함.
ver 2.
class SpinLock
{
private:
atomic<bool> _locked = false;
public:
void lock()
{
// CAS(Compare-And-Swap)
bool expected = false;
bool desired = true;
/*
* CAS 의사 코드(커널 레벨에서 동작하는 것이므로 깊게 생각할 필요 없음)
* if(_locked == expected) 락 획득 성공
* {
* expected = _locked;
* _locked = desired;
* return true;
* }
* else 락 획득 실패
* {
* expected = _locked;
* return false;
* }
*/
while (_locked.compare_exchange_strong(expected, desired) == false)
{
expected = false; //expected는 참조를 갖기 때문에 값이 계속 변함 -> 계속 초기화해줘야 함
}
/*
while(_locked)
{
//바쁜 대기
}
_locked = true;
*/
}
void unlock()
{
_locked.store(false); //atomic하게 대입
}
}
⭐️ TIP
atomic<typename T>
에는 volatile이 포함되어 있다!
bool compare_exchange_strong(bool& _Expected, const bool _Desired)
실행 결과
0
참조 링크
CAS 란?