SpinLock

원래벌레·2022년 4월 10일
0

💎 volatile

  • C++ 에서는 컴파일러에게 최적화 하지 말아 달라는 뜻이다.

  • 여기서 최적화를 해주지 말라는 말은 예시를 들면 쉽다.

int32 a=0;
a=1;
a=2;
a=3;
a=4;

이렇게 값이 변하는 a라는 변수가 있다고 할 때, Debug 모드가 아닌 Realease 모드에서 이를 돌리면 컴파일러는 int32 a=4;와 같은 형태로 답을 바꾼다.

  • 이렇게 보면 a=0, a=1, a=2, a=3과 같은 일이 필요 없어 보이지만, 실제로 이것이 프로그래머의 의도 였고 꼭 필요한 코드 였다면 이건 문제가 된다.

  • 따라서 이를 해결하기 위해서 변수에 volatile를 선언해주면 최적화를 진행하지 않고, 코드가 쳐진 그대로 나오게 된다.

volatile int32 a=0;
a=1;
a=2;
a=3;
a=4;
  • 그러면 이러한 volatile이 왜 SpinLock에서 필요한지 살펴보면
bool locked;
While(locked){
	
}
locked=True;

위와 같이 locked는 자물쇠가 설정 되어있으면 True를 안걸려 있다면 False를 가지고 있는 bool 변수이다. 위 코드에서 현재 locked의 값이 True라면, unlock 메소드가 들어 올 때 까지 계속해서 while문을 통해 locked가 True인지 False인지 확인을 해야한다.

  • 하지만 위의 경우 컴파일러는 하나의 메소드 안에 지금 bool의 값이 변화하지 않고 있기 때문에 while문을 최적화를 해주게 된다. 여기서 while문은 두가지의 행동을 하는데 한가지는 조건문을 확인하고 그리고나서 반복을 해주는 행동을 한다.

  • 그런데 컴파일러가 최적화를 해주지 않으면 조건문을 확인하는 부분이 사라지고 jmp 명령만을 하게 된다.

  • 이를 방지하기 위해서 SpinLock에서 volatile을 사용해준다.

💎 SpinLock 구현

#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
#include <mutex>
using namespace std;


class SpinLock
{
public:
	void lock()
	{
		while (_locked)
		{

		}
		_locked = true;
	}
	void unlock()
	{
		_locked = false;
	}
private:
	volatile bool _locked = false;
};

int32 sum = 0;
mutex m;
SpinLock spinLock;

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() {
	thread t1(Add);
	thread t2(Sub);

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

	cout << sum << endl;
}
  • 위와 같이 코드를 입력하면 될 것 같지만, 저렇게 입력을 하면 값이 제대로 안나온다.

  • 그 이유는 while문이 위에서도 말했듯이 두가지의 일을 하기 때문이다.

  • while문이 어셈블러 언어를 두개를 실행하면서 그 사이에 두개의 쓰레드가 한꺼번에 들어와서 자신이 먼저 들어왔다고 주장을 하게 된다.

  • 이러한 상황에서 충돌하게 된다.

  • 따라서 이를 해결해주기 위해서는 while문을 atomic 개념으로 묶어주어야 한다.

💎 CAS

#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
#include <mutex>
using namespace std;


class SpinLock
{

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;
		}
	}
	void unlock()
	{
		_locked.store(false);
	}
private:
	atomic<bool> _locked = false;
};

int32 sum = 0;
mutex m;
SpinLock spinLock;

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() {
	thread t1(Add);
	thread t2(Sub);

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

	cout << sum << endl;
}
  • CAS ( compare - and - swap )

  • 이제 이 while문을 atomic 메소드인 compare_exchange_strong을 통하여 atomic 하게 명령어를 실행해준다.

  • 저 메소드가 하는 일은 expected와 desired 인수를 받는데, expected 인수는 내가 받았으면 하는 if문 값이고, desired는 인수를 받고 나서 어떤 값으로 변했으면 하는지 하는 값이다. 이에 자세한 내용은 위코드에 CAS 의사코드를 살펴 보면 된다.

    새로운 표현
    lock_guard<SpinLock> guard(spinLock);
    _locked.store(false);
    while (_locked.compare_exchange_strong(expected,desired)==false)
profile
학습한 내용을 담은 블로그 입니다.

0개의 댓글