문제

내가 처음 IOCP 서버를 다루기 시작할 때, 다음과 같은 코드가 이상해 보였다.

	void ProcPack()
	{
		shared_ptr<Packet> pPack = nullptr;

		char* pStart = nullptr;
		UINT8 Size;

		while (m_bIsRunProcThread)
		{
			if (!m_PackQ.Dequeue(pPack))
			{
				this_thread::sleep_for(chrono::milliseconds(1)); // made it wait, not sleeping for arbitrary time
				continue;
			}
			
            // ... do something
		}
	}

분명 스레드를 신호에 따라 멈추고 다시 동작하게 만드는 함수들이 있는데도,
1 ms만 쉬고 다시 다음 라운드를 돌게 만드는 거였다.
왜?
SNS에 질문해 보니 누군가가 '시스템 콜'에 대해 알아보라고 했다.

해결

위 코드에서 1 ms 쉬고 다시 다음 라운드에서 유의미한 작업을 할지 다시 판단하는 걸 보고 'Busy Wait'라 부른다.

왜 이런 행위를 하냐!

저 코드는 동작하는 스레드의 멈춤/실행을 제어한다.
'기다려' 했을 때 정말 스레드가 잠자기에 들어가는 게 아니라,
1 ms 쉬고 다시 검사하면서 계속 돌아가는 식.
요즘 러닝이 대세던데, 횡단보도 빨간 불 앞에서 러너들이 제자리 뛰기를 하며 기다리는 상황이라 보면 된다.
러너는 달리기 페이스가 깨지지 않기 위해 이렇게 하는 것 같은데, 스레드에서 Busy Wait를 하는 이유도 비슷하다.

만약 스레드가 잠시 동작을 멈추도록 std::mutex으로 객체를 잠그거나 std::condition_variable 등을 사용하면,
CPU는 라운드를 돌다 말고 커널에 들어가 스레드를 재운다.
깨울 때도 마찬가지.
이를 시스템 콜, 커널 호출이라 부른다.
딱 들어만 봐도 Busy Wait로 무의미한 반복문을 도는 것보다 오버헤드가 커 보인다.
반면 Busy Wait는 무의미한 반복문을 도는데 CPU가 따끈해지는 단점이 있다.

Busy Wait로 구현할 경우 프로세스는 사용자 모드(user mode)에서만 동작.
반면 시스템 콜이 일어나는 std::mutex, std::condition_variable커널 모드(kernel mode)에서 동작이 일어난다.

또, 위 코드에서 Busy Wait를 할 시 1 ms를 쉬고 다시 돌게 만들었는데,
만약 쉬지 않고 계속 조건문을 검사하도록 하면 CPU엔 조건문 검사하는 작업만 스케쥴링 된다.
저 쉬고 오는 동안 컨텍스트 스위칭을 하여 다른 작업들도 할 수 있도록 한 것이다.

그러면, 실무에서 다음과 같이 판단할 수 있을 것이다.
실행/잠자기 전환이 빈번하다? -> Busy Wait!!!
빈번하지 않고, 하나의 라운드가 무거운 작업이다? -> std::mutex, std::condition_variable과 같은 시스템 콜이 있는 도구들!!!

내 서버 코드에서 Busy Wait를 없애면?

profile
C++ Game Developer

0개의 댓글