멀티스레드 환경에서의 락(Lock) : SpinLock, Sleep, Event

나무에물주기·2023년 5월 8일
1
post-thumbnail

SpinLock 이란?

SpinLock은 락을 획득할 수 있을 때까지 스레드가 루프를 돌며 기다리는 동기화 기법이다. 잠금을 얻을 때까지 스레드가 계속해서 CPU를 사용하므로, 짧은 기다림 시간에 효율적이다. 하지만, 장시간 기다릴 경우 CPU 자원 낭비가 심해질 수 있다.

SpinLock 예제 코드

아래 코드는 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의 단점 및 개선 방법

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();
}

다른 동기화 방법

  1. Sleep을 활용한 Lock: SpinLock과 비슷하지만, 루프 안에서 스레드를 잠시 멈추어 CPU 자원을 절약한다. 일정 시간마다 Lock 획득 시도를 함으로써 다른 스레드에게 CPU 사용 기회를 준다.

  2. Event를 활용한 Lock: 스레드가 Event의 신호 상태를 기다리며, 다른 스레드가 신호를 보낼 때까지 대기한다. 이벤트를 사용하면, 스레드가 신호 상태가 될 때까지 일시 중단되어 CPU 자원을 효율적으로 사용할 수 있다.

Event를 활용한 Lock 예제 코드

// ... 

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 자원을 효율적으로 사용할 수 있다.

profile
개인 공부를 정리함니다

0개의 댓글