오구의코딩모험

[Server] SpinLock, Sleep, Event 본문

Game/Server

[Server] SpinLock, Sleep, Event

오구.cpp 2025. 3. 17. 20:53
반응형

 

 

서버 개발에서 배우는 동기화 기법

 


서버 개발에서는 성능 최적화와 동기화를 위한 다양한 기법이 사용됩니다.

이번 글에서는 volatile, SpinLock, Sleep, Event (Auto Reset Event / Manual Reset Event) 등의 학습한 내용을 정리합니다.

 


 

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


1. volatile: 컴파일러 최적화 방지

volatile이란?

- volatile 키워드는 컴파일러의 최적화를 방지하는 기능을 합니다.  
- 일반적인 코드에서 불필요한 대입 연산이 제거되지만, volatile을 선언하면 모든 연산이 실행됩니다.

 

예제 개념

- 변수 a가 여러 번 대입되는 경우, 컴파일러는 불필요한 연산을 제거하여 최적화합니다.  
- 하지만 volatile을 사용하면 모든 대입이 유지되며, 실시간 데이터 변경이 필요한 경우 유용합니다.  

 

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 = false;
		_locked.store(false);
	}

private:
	atomic<bool> _locked = false;
};

 

2. SpinLock: 빠른 락 구현

SpinLock이란?

- SpinLock은 하나의 스레드만 점유할 수 있도록 락을 구현하는 방식입니다.  
- atomic 연산을 사용하여 스레드가 lock을 획득할 때까지 반복 시도합니다.  
- CAS(Compare-And-Swap) 기법을 활용하여 lock을 atomic하게 획득합니다.  

 

SpinLock의 특징

- 컨텍스트 스위칭 없이 유저 모드에서 반복 실행되므로, 빠르게 lock을 해제할 경우 유리합니다.  
- 그러나 긴 시간이 걸리는 작업에서는 CPU 점유율이 높아지는 단점이 있습니다.  

 

 

// (= this_thread::sleep_for(std::chrono::milliseconds(100)); )
this_thread::sleep_for(100ms);
this_thread::yield();

 

3. Sleep과 Yield: 실행 소유권 반환

Sleep이란?

- Sleep은 실행 소유권을 반납하고, 지정한 시간 동안 재스케줄링이 이루어지지 않도록 합니다.  
- this_thread::sleep_for(100ms)와 같은 함수로 사용됩니다.  

 

Yield란?

- Yield는 현재 타임슬라이스에서 실행할 필요가 없을 때 실행 소유권을 반환합니다.  
- 즉시 재스케줄링이 가능하지만, 현재 시점에서 실행할 필요가 없다고 판단될 경우 반환됩니다.  
- this_thread::yield()를 사용하며, sleep_for(0ms) 와 유사한 효과를 가집니다.  

 

Sleep vs Yield 비교

- Sleep : 일정 시간 동안 재스케줄링을 방지하며, 일정 시간 동안 실행을 중단.  
- Yield : 즉시 실행을 중단하고 다른 스레드에게 CPU를 양보할 수 있도록 함.  

 

 

// 커널 오브젝트
// Usage Count
// Signal / Non-Signal << bool
// Auto / Manual << bool
handle = ::CreateEvent(NULL/*보안속성*/, FALSE/*bManualReset*/, FALSE/*bInitialState*/, NULL);

::CloseHandler(handle);

 

4. Event (Auto Reset Event / Manual Reset Event)

Event란?

- Event는 특정 작업이 실행 가능할 때 시그널을 보내 실행을 유도하는 기법입니다.  
- 커널 개입이 필요하므로 추가적인 비용이 발생하지만, 정확한 제어가 가능합니다.  

 

Event의 특징

- Windows API의 CreateEvent를 사용하여 생성됩니다.  
- SetEvent를 이용하여 시그널을 보내고, WaitForSingleObject를 통해 시그널 변경을 확인합니다.  
- 사용 후 CloseHandle을 호출하여 자원을 정리해야 합니다.  

 

Auto Reset Event  vs  Manual Reset Event

- Auto Reset Event
  * 시그널이 감지된 후 자동으로 Non-Signal 상태로 변경됩니다.  
  * 스레드가 이벤트를 처리한 후, 다른 스레드가 다시 실행되도록 설계됨.  
- Manual Reset Event
  * 시그널을 수동으로 변경해야 합니다.  
  * 여러 스레드가 같은 이벤트를 감지하도록 유지할 수 있음.

 

 


 

정리


1. volatile 사용 시 주의
   - 메모리 최적화를 방지하지만, 모든 연산이 실행되므로 과도한 사용은 피해야 합니다.  

2. SpinLock 활용
   - 빠르게 lock을 해제할 경우 효과적이지만, 장시간 점유 시 CPU 부하가 증가할 수 있습니다.  

3. Sleep과 Yield 적절한 선택
   - Sleep은 명확한 시간 대기 시 사용하고, Yield는 불필요한 타임슬라이스 낭비를 줄이는 용도로 사용합니다.  

4. Event 사용 시 고려할 점
   - 커널 개입이 필요하여 자원 소모가 발생하므로, 너무 자주 호출하지 않도록 설계해야 합니다.  

 

반응형
Comments