Skip to main content
  1. Posts/

Game Server Development #7 : Sleep

Korean Post Programming C++ Server Thread Lock
Table of Contents
Game Server Development - This article is part of a series.
Part 7: This Article

Material #

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버

Introduction #

이전 글에서 while 문 내부에서 CAS 체크를 통해 SpinLock 을 구현하는 방법에 대해서 알아봤었다.

Game Server Development #6 : Spin Lock
Korean Post Programming C++ Server Thread Lock

while 문을 통해 지속적으로 락을 획득하려고 시도하는 방식에는 CPU 자원을 낭비하는 문제가 있다.

이를 어느정도 막기 위해 Sleep 을 사용하여 현재 스레드의 동작을 잠시 멈추게 할 수 있다.

멈춘 스레드는 일정 시간이 지나면 다시 동작하게 되며, 멈춰있는 동안 CPU 를 사용하지 않기 때문에 CPU 자원을 낭비하지 않게 된다.

Sleep #

sleep 은 특정 쓰레드 또는 프로세스를 지정된 시간 동안 말 그대로 잠재운다. (일시적으로 중단한다)

호출되면, 해당 쓰레드는 설정된 시간 동안 일을 중단하고, 그 동안 CPU는 다른 스레드의 작업을 수행한다.

주로 정확한 시간 동안 쓰레드를 중지하기 위해 사용된다.

C++ 11 부터 std::this_thread::sleep_for 를 통해 스레드를 잠시 멈출 수 있다.

#include <iostream>
#include <thread>
#include <chrono>

int main()
{
	std::cout << "Start" << std::endl;

	std::this_thread::sleep_for(std::chrono::seconds(1));

	std::cout << "End" << std::endl;

	return 0;
}

위 코드는 Start 를 출력하고, 1초 동안 멈춘 뒤 End 를 출력한다.

Yield #

yield 는 현재 실행 중인 쓰레드가 실행을 양보하고, 동일한 우선순위를 가진 다른 쓰레드에게 실행 기회를 제공한다.

호출할 경우, 현재 쓰레드는 준비 상태로 전환되고, 스케줄러는 동일한 우선순위의 다른 쓰레드를 실행한다.

만약 동일한 우선순위의 쓰레드가 없다면, yield 를 호출한 쓰레드는 계속해서 실행을 이어간다.

C++ 11 부터 std::this_thread::yield 를 통해 현재 스레드를 일단 멈추고, 다른 스레드에게 CPU 를 양보할 수 있다.


#include <iostream>
#include <thread>
#include <chrono>

int main()
{
	std::cout << "Start" << std::endl;

	std::this_thread::yield();

	std::cout << "End" << std::endl;

	return 0;
}

위 코드는 Start 를 출력하고, 다른 스레드에게 CPU 를 양보한 뒤, 다시 실행하게 된다면 End 를 출력한다.

Time Slice #

Time Slice 는 CPU 가 스레드에게 할당하는 시간을 의미한다.

스레드는 Time Slice 를 모두 소진하면, 다른 스레드에게 CPU 를 양보하게 된다.

Time Slice 는 운영체제에 의해 관리되며, Time Slice 가 끝나기 전에 스레드가 끝나게 되면, 다른 스레드에게 CPU 를 양보하지 않고 계속해서 CPU 를 사용하게 된다.

Spint Lock (Sleep) #

이전에 작성했던 코드에서 SpinLock 을 사용할 때, while 문 내부에서 Sleep 을 사용하여 CPU 자원을 낭비하지 않도록 할 수 있다.

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <chrono>

class SpinLock
{
public:
	void lock()
	{
    	bool expected = false;
    	bool desired = true;

		// CAS (Compare-And-Swap)
   		while (_locked.compare_exchange_strong(expected, desired) == false)
		{
			expected = false;

			// 1. chrono 라이브러리를 사용하여 100ms 동안 스레드를 멈춘다.
      		std::this_thread::sleep_for(std::chrono::milliseconds(100));
			
			// 2. 아래처럼 ms 를 사용하여 100ms 동안 스레드를 멈출수도 있다. 결론적으로 위와 같은 코드다.
			std::this_thread::sleep_for(100ms);

			// 3. yield 를 사용하여 현재 스레드를 일단 멈추고 다음 스케쥴을 기다릴 수 있다. (언제 다시 돌아올지 모름)
			std::this_thread::yield();
		}
	}

	void unlock()
	{
		_locked.store(false);
	}

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

int sum = 0;
SpinLock spinLock;

void Add()
{
	for (int i = 0; i < 100'0000; ++i)
	{
		std::lock_guard<SpinLock> guard(spinLock);
		sum++;
	}
}

void Sub()
{
	for (int i = 0; i < 100'0000; ++i)
	{
		std::lock_guard<SpinLock> guard(spinLock);
		sum--;
	}
}

int main()
{
	std::thread t1(Add);
	std::thread t2(Sub);

	t1.join();
	t2.join();

	std::cout << sum << std::endl;

	return 0;
}
Game Server Development - This article is part of a series.
Part 7: This Article

Related

Game Server Development #6 : Spin Lock
Korean Post Programming C++ Server Thread Lock
Game Server Development #5 : Lock Implementation Theory
Korean Post Programming C++ Server Thread Lock
Game Server Development #4 : Dead Lock
Korean Post Programming C++ Server Thread Lock
Game Server Development #3 : Lock
Korean Post Programming C++ Server Thread Lock
Game Server Development #9 : Condition Variable
Korean Post Programming C++ Server Thread Condition Variable
Game Server Development #8 : Event
Korean Post Programming C++ Server Thread Event