SpinLock은 락을 획득할 수 있을 때까지 스레드가 루프를 돌며 기다리는 동기화 기법이다. 잠금을 얻을 때까지 스레드가 계속해서 CPU를 사용하므로, 짧은 기다림 시간에 효율적이다. 하지만, 장시간 기다릴 경우 CPU 자원 낭비가 심해질 수 있다.
아래 코드는 SpinLock 클래스를 사용하여 멀티스레드 환경에서 공유 데이터를 안전하게 처리하는 방법을 보여준다. 원자적으로 _locked 변수의 값을 변경하기 위해 compare_exchange_strong 메서드를 사용하며, 경쟁 조건(race condition)을 방지한다.
// ...
class SpinLock
{
public:
void lock()
{
bool expected = false;
bool desired = true;
while (_locked.compare_exchange_strong(expected, desired) == false)
{
expected = false;
}
}
void unlock()
{
_locked.store(false);
}
private:
std::atomic<bool> _locked = false;
};
// ...
SpinLock의 단점은 스레드가 계속해서 CPU를 사용하므로 CPU 사용률이 높아질 수 있다는 점이다. 이를 개선하기 위해 sleep_for 및 yield 함수를 사용하여 스레드가 일정 시간 동안 대기하거나 자발적으로 CPU를 양보하는 방법을 제공할 수 있다. 이를 통해 다른 스레드가 lock을 얻을 수 있는 기회를 제공하고, CPU 사용률을 낮출 수 있다.
while (_locked.compare_exchange_strong(expected, desired) == false)
{
expected = false;
this_thread::sleep_for(0ms);
// 또는
// this_thread::yield();
}
Sleep을 활용한 Lock: SpinLock과 비슷하지만, 루프 안에서 스레드를 잠시 멈추어 CPU 자원을 절약한다. 일정 시간마다 Lock 획득 시도를 함으로써 다른 스레드에게 CPU 사용 기회를 준다.
Event를 활용한 Lock: 스레드가 Event의 신호 상태를 기다리며, 다른 스레드가 신호를 보낼 때까지 대기한다. 이벤트를 사용하면, 스레드가 신호 상태가 될 때까지 일시 중단되어 CPU 자원을 효율적으로 사용할 수 있다.
// ...
HANDLE handle; // Event 핸들 선언
void Producer()
{
while (true)
{
{
unique_lock<mutex> lock(m);
q.push(100);
}
::SetEvent(handle); // Event를 신호 상태로 설정하여 Consumer에게 알림
this_thread::sleep_for(10000ms);
}
}
void Consumer()
{
while (true)
{
::WaitForSingleObject(handle, INFINITE); // 신호 상태가 될 때까지 대기
unique_lock<mutex> lock(m);
if (q.empty() == false)
{
int32 data = q.front();
q.pop();
cout << data << '\n';
}
}
}
// ...
Event를 활용한 Lock 방식은 다른 두 방식과 달리, 스레드가 신호 상태가 될 때까지 일시 중단되기 때문에 CPU 자원을 더 효율적으로 사용할 수 있다. 이로 인해 시스템의 전반적인 성능이 향상될 수 있다. 그러나 Event 객체를 사용하면 코드가 복잡해질 수 있으며, Windows API와 같은 플랫폼 종속적인 기능을 사용해야 한다.
SpinLock은 짧은 기다림 시간에 효율적이지만, 장시간 기다릴 경우 CPU 자원 낭비가 심해질 수 있다. 이러한 단점을 해결하기 위해 sleep_for 또는 yield 함수를 사용하여 스레드가 일정 시간 동안 대기하거나 자발적으로 CPU를 양보하는 방법을 제공할 수 있다. 또한 Event를 활용한 Lock 방식을 사용하면, 스레드가 신호 상태가 될 때까지 일시 중단되어 CPU 자원을 효율적으로 사용할 수 있다.