서버

DeadLock과 Lock구현

yeonii_ 2025. 4. 7. 21:12

DeadLock이란?

둘 이상의 프로세스 혹은 스레드가 상대방이 점유한 자원을 대기하며 무한정 기다리는 상황이다.

발생조건

  • 상호배제(Mutual Exclusion) - 자원은 하나의 프로세스만 사용 할 수 있다.
  • 점유대기(Hold and Wait) - 최소한 하나의 자원을 점유하고 있으면서 다른 프로세스에 할당되어 사용하고 있는 자원을 추가로 점유하기 위해 대기중이어야 한다.
  • 비선점(No Preemption) - 다른 프로세스에 할당된 자원은 사용이 끝날때까지 강제로 빼앗을 수 없다.
  • 순환 대기(Circular Wait) - 프로세스들이 서로 자원을 기다리며 순환적인 대기 관계여야 한다.

SpinLock이란?

경쟁 상태 상황에서 Lock이 반환될때까지 즉 임계영역에 진입 가능할 때까지 프로세스가 계속 재시도하며 대기하는 상태이다.

스핀락은 context switching이 일어나지 않아 CPU의 부하를 줄일 수 있다는 장점이 있지만, lock이 반환 될때까지 루프를 돌면서 계속 확인해야 한다는 단점이 있다.

 

구현을 위해서 CAS에 대해서 살펴보자.

CAS(Compare - And - Swap) 연산이란?

atomic하게 이루어지는 연산으로특정 메모리위치의 값이 주어진 값과 동일하다면 해당 메모리 주소를 새로운 값으로 대체해주는 연산이다.

  • compare_exchange_weak - 실패할 경우 자동으로 다시 시도할 수 있는 연산이다. 실패가 자주 발생할 수 있는 환경에서 유용
  • compare_exchange_strong - 실패시 자동으로 시도하지 않으며 호출자가 연산을 재시도해야한다. 하지만 weak보다 더 강력한 처리를 제공한다.
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);    
    }
}

Sleep Lock이란?

스레드가 자원을 얻을 수 없을 때, CPU를 낭비하지 않고 Sleep 상태로 전환되어 다른 스레드가 실행 될 수 있도록 하는 방식이다. 일정 시간마다 자원을 획득할 수 있는지 확인하며 기다린다. SpinLock과는 다르게 시스템 콜을 호출하여 컨텍스트 스위칭이 일어난다.

class SpinLock
{
public:
    void lock()
    {
    	bool expected = false;
        bool desired = true;
        while(_locked.compare_exchange_strong(expected, desired) == false)
        {
            expected = false;
            this_thread::sleep_for(3ms);
            //this_thread::sleep_for(std::chrono::milliseconds(300));
        }
    void unlock()
    {
        _locked.store(false);    
    }
}

Event Lock이란?

스레드가 자원을 얻을 수 없을 때 특정 이벤트가 발생할 때까지 대기하거나, 특정 조건이 충족되었을때 스레드를 깨워 실행 될 수 있도록 하는 방식이다. 이벤트 객체를 사용하여 동작한다.

대기 상태로 들어갈 때, 이벤트를 받고 깨워져 실행 상태로 전환될 때 컨텍스트 스위칭이 발생한다.

void Producer()
{
    while(true)
    {
    	{
    	    unique_lock<mutex> lock(m);
            q.push(100);
    	}
        SetEvent(handle);
        this_thread::sleep_for(10000ms);
    }	
}

void Consumer()
{
    while(true)
    {
    	WaitForSingleObject(handle, INFINITE);
        
        unique_lock<mutex> lock(m);
        if(q.empty() == false)
        {
            int data = q.front();
            q.pop();
        }
    }
}

int main()
{
    handle = CreateEvent(NULL/*보안속성*/,FALSE/*bManualReset*/,FALSE/*bInitialState*/,NULL);
    thread t1(Producer);
    thread t2(Consumer);
    
    t1.join();
    t2.join();
}
  • bManualReset - 이벤트 객체가 자동리셋인지 수동리셋인지 지정
    • TRUE - 수동 리셋, 이벤트가 신호 상태로 변경된 후 명시적으로 ResetEvent를 호출해 비신호 상태로 돌아
    • FALSE - 자동 리셋, 이벤트 객체가 신호 상태로 변경되면, 하나의 스레드를 깨운 후 자동으로 비신호 상태로 돌아감
  • bInitialState - 이벤트 객체가 생성될때 초기 상태를 설정
    • TRUE - 이벤트 객체는 신호 상태로 생성, 스레드가 즉시 실행
    • FALSE - 비신호 상태로 생성, 신호 상태가 될때까지 대기

'서버' 카테고리의 다른 글

std::future  (0) 2025.04.08
조건 변수 Condition Variable  (0) 2025.04.08
스레드와 멀티스레드  (1) 2025.04.05