게임 서버 개발 공부를 하는 중 제가 너무 클라이언트(유니티) 관련 구현에 치중된 것을 알게되었습니다.
그래서 다시 게임 서버 관련 강의를 들으면서 정리하기 위해 글을 작성하게 되었습니다.
게임 서버에서 멀티 스레드를 사용해 처리 속도의 효율성을 올린다고 합니다.
우선 멀티 스레드에 대해 이해하기 전에 스레드에 대해 알기위해 공부하게 되었습니다.
C#으로 게임 서버를 만들어보고 있기 때문에 예제코드는 C# 관련 스레드 코드로 작성되어있습니다.
어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.
<위키백과 - 스레드(컴퓨팅)>
프로세스는 컴퓨터에서 실행되고 있는 컴퓨터 프로그램을 말합니다.
운영 체제에서는 이 프로세스를 관리하며, 각 프로세스는 자체 메모리 공간과 실행 상태,
프로그램 카운터 등의 정보를 가집니다.
요즘은 대부분 멀티 스레드 방식을 사용해 프로그램을 만들고 있습니다.
특히 게임 서버 쪽은 이런 멀티 스레드의 중요성이 상당히 큽니다.
각 플레이어의 액션, 게임 내의 이벤트, 데이터 동기화 등 다양한 작업들을 동시에 처리해야 하기 때문입니다.
C#에서는 어떻게 스레드를 생성할 수 있을지 코드를 찾아봤습니다.
class Program
{
// 스레드가 실행할 메서드
static void MainThread()
{
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
// 스레드 생성 시 인자로 실행할 함수를 꼭 넣어줘야 합니다.
Thread thread = new Thread(MainThread);
// 생성한 스레드를 실행합니다.
thread.Start();
}
}
코드는 생각했던 것보다 훨씬 간단합니다.
위 코드를 실행하면 아래와 같이 콘솔 창에 출력이 됩니다.
여기서 C#에서 스레드를 생성할 때의 특징이 있다고 합니다.
백그라운드 스레드로 사용할 지, 포그라운드 스레드로 사용할지는 직접 선택할 수 있습니다.
백그라운드 스레드
class Program
{
static void MainThread()
{
while (true)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
Thread thread = new Thread(MainThread);
thread.IsBackground = true;
thread.Start();
Console.WriteLine("대기중");
Console.WriteLine("스레드 종료");
}
}
위 코드의 출력 결과를 보면 아래와 같습니다.
백그라운드 스레드는 메인 스레드가 종료되면 종료 하는 것을 알 수 있습니다.
포그라운드 스레드
class Program
{
static void MainThread()
{
while (true)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
Thread thread = new Thread(MainThread);
thread.IsBackground = false;
thread.Start();
Console.WriteLine("대기중");
Console.WriteLine("스레드 종료");
}
}
위 코드의 출력 결과를 보면 아래와 같습니다.
반면에 포그라운드 스레드는 메인 스레드가 종료 되어도 while 반복문이 계속 실행됩니다.
포그라운드 스레드는 주로 사용자 인터페이스와 관련도니 작업에 사용되며,
백그라운드 스레드는 비동기 및 보조 작업에 사용됩니다.
이렇게 스레드를 나눔으로써 병렬성을 활용하면서도 안정성을 유지할 수 있습니다.
멀티 스레드 프로그래밍에서 중요한 것 중 하나는 스레드를 관리하는 것에 있습니다.
단순히 스레드를 많이 생성해서 작업자들이 많아져 성능이 향상되지 않습니다.
고려해야 할 부분과 까다로운 점이 많다고 합니다.
개발자가 직접 멀티 스레드를 관리한다는 것이 상당히 어렵다는 것을 알게되었습니다.
이런 어려움을 어느정도 도움을 주기 위해 C#에서는 스레드 풀
스레드 풀이란 스레드를 미리 생성하고, 작업 요청이 발생할 때마다 미리 생성된 스레드로 해당 작업을 처리하는 방식을 의미합니다.
C#에서는 이런 스레드 풀을 제공해줍니다.
그래서 직접 구현하는 번거로움을 덜어줍니다.
class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 5; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
// 스레드풀에 있는 스레드를 이용하여 MainThread() 메서드 실행
ThreadPool.QueueUserWorkItem(MainThread);
Console.WriteLine("Finish");
}
}
class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 5; i++)
Console.WriteLine($"Hello Thread!");
}
static void Main(string[] args)
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(5, 5);
for (int i = 0; i < 4; i++)
ThreadPool.QueueUserWorkItem((obj) => { while (true) { } });
ThreadPool.QueueUserWorkItem(MainThread);
Console.WriteLine("Finish");
}
}
사용할 스레드를 세팅하고 그 무한 반복문 작업을 실행시켜 먹통을 만드는 코드입니다.
실행을 시키면 위와 같이 스레드풀에서 실행 할 MainThread 작업은 실행되지 않고 종료가 됩니다.
이렇듯 스레드풀을 사용할 때는 작업의 길이를 잘 고려해서 사용해야 합니다.
자바, 스프링을 사용하면서 스레드를 직접 생성하거나 스레드를 고려해서 프로그래밍을 해본적이 없었습니다.
이번에 게임 서버 프로그래밍을 공부 하면서 직접 만들어보면서 이해가 훨씬 잘 되었습니다.
그리고 고려해야 할 부분과 얼마나 번거로운지 매번 알게 됩니다.
스레드풀이 이런 번거로움을 보완한다고 하지만, 스레드풀도 잘못 사용하면 먹통이 되어서 역효과가 발생한다는 것도 알았습니다.
이번에 공부를 하면서 프로세스와 스레드의 차이도 명확하게 이해를 하게 되었습니다.
확실히 코드를 직접 작성하고 출력을 해서 확인하는 단계가 정말 중요한 것 같습니다.