반응형
공유된 자원에 여러 개의 쓰레드가 동시 접근 하는 문제를 해결하기 위해선 여러가지 방법들이 있다.
첫 째로, Lock이 해제 될 때 까지 무한히 기다리는 방법이 있을 것이며 이것이 오늘 설명할 스핀락의 기조다.
둘 째로 콜백 방식이 있을 것이며
셋 째로 특정 시간 뒤에 다시 찾아가는 방식이 있다.
여러 방식 중 오늘은 첫 번째 방식인 스핀락에 대해 설명하고자 한다.
간단히 그림과 같이 2개의 쓰레드가 공유된 자원에 접근 시, Task A가 먼저 접근하였다고 가정하자.
Task B는 A가 unlock을 할 때 까지 무한히 대기하다가, unlock이 되면 접근하는 구조다.
문제 사항으로는 당연히, Task B는 쓸대 없이 A가 끝날 때 까지 기다려야 하므로 오버헤드가 발생하고
CPU 점유율에 영향을 미친다.
class SpinLock {
public:
// lock guard에서 소문자로 자동실행하기 때문에 시그니쳐를 맞춰줘야 함.
void lock()
{
while(_locked)
{
}
_locked = true;
}
void unlock()
{
_locked = false;
}
private:
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;
}
Spin Lock을 직접 구현할 때, 아무 생각없이 위와 같이 작성하면 동기화가 제대로 작동하지 않는다.
이유를 보자면, 서로 동시에 입장하는 경우가 생길 수 있기 때문에, 이럴 땐 Atomic 하게 설계를 해야한다.
하지만 while과 locked를 true로 바꿔주는 부분은 한 줄로 의사코드를 표현할 수 없다.
C++ atomic에선, 이러한 것을 CAS ( Compare - And - Swap ) 을 제공한다.
_locked.compare_exchange_strong()
해당 CAS를 의사코드로 표현하자면 아래와 같다.
bool expected = false;
bool desired = true;
//CAS 의사코드
if (_locked == expected)
{
_locked = desired;
return true;
}
else {
expected = _locked;
return false;
}
class SpinLock {
public:
void lock()
{
//CAS ( Compare - And - Swap )
// 한 번에 실행이 되어야 한다. 두 쓰레드가 동시에 들어와 버리기 때문에.
bool expected = false;
bool desired = true;
while (_locked.compare_exchange_strong(expected, desired)== false)
{
expected = false;
}
}
void unlock()
{
//_locked = false;
_locked.store(false);
}
private:
atomic<bool> _locked = false;
};
하여 전체적인 Spin Lock 코드를 보자면 위와 같이 작성할 수 있다.
반응형
'프로그래밍-기본기 > C++' 카테고리의 다른 글
(쓰레드) Mutex (0) | 2023.09.08 |
---|---|
(쓰레드) Atomic (0) | 2023.09.06 |
스마트 포인터 (1) | 2023.09.05 |