23.Thread Pool, Thread Model 설정하기

프라우드넷의 네트워크 클라이언트 모듈 NetClient과 서버 모듈 NetServer는 두 종류의 스레드 풀(Thread pool)을 사용합니다.

표 23-1프라우드넷의 스레드 풀

Networker thread pool

User worker thread pool

프라우드넷 내부 처리 전용으로 사용

소켓 I/O 처리 등을 담당

사용자가 정의한 루틴을 실행하는 용도로 사용

RMI나 콜백 이벤트 등을 처리

23.1Thread Pool 설정하기

프라우드넷은 기본적으로 각 모듈별로 CPU 갯수만큼의 networker thread, user worker thread가 만들어집니다. 스레드의 개수가 다소 많더라도 사용자의 실수에 대한 방탄 처리를 더 우선으로 하여 동작하기 때문입니다.

user worker thread에서 딜레이가 발생하는 처리, 가령 데이터베이스나 파일 액세스를 하는 것이 너무 많은 경우가 있죠. 이 딜레이가 networker thread에 영향을 주게 되면, 다양한 오동작이 발생할 수 있습니다.

하지만, 만약 서버 개발에서 실수를 전혀 하지 않았다는 것이 보장되는 상황, 예를 들어 사용자 정의 루틴이 큰 딜레이가 없게 만들어졌다면, networker thread와 user worker thread를 합치거나, 스레드 수를 조절해서, 성능을 더 내게 만드는 게 가능합니다.

스레드 각각은 소켓 I/O와 사용자 정의 루틴을 한꺼번에 처리하게 되는 거죠. 프라우드넷에서는 설정에 따라 다양한 스레드 모델을 구성할 수 있습니다.

개요

최상의 성능을 내기 위해 이들 thread pool을 각 모듈들이 소유하는 대신 특정한 thread pool이 공유되게 만들고 싶을 수 있습니다. 여기서는 어떻게 그렇게 하는지를 설명합니다.

스레드 풀을 명시적으로 만들고 네트워크 모듈에 셋팅합니다. 아래는 전체적인 흐름에 관련된 pseudo code입니다.

사용자가 생성한 thread pool 객체는 네트워크 모듈들이 모두 파괴되기 전에는 파괴되어서는 안됩니다. 그렇지 않으면 throw exception이 발생합니다.

현재 클라이언트 단에 이 기능을 사용하시려면 ProudNetServer.h를 include 해주셔야 합니다.

1.7.36365 이후 부터 ProudNetServer.h 를 include 하지 않아도 사용 가능합니다.

// 스레드풀 시작/종료에 대한 콜백을 담당하는 객체입니다.  
CThreadPoolEventFunctional e;  
  
//즉, 스레드풀 시작/종료에 대한 루틴을 넣습니다. (선택 사항)  
e.OnThreadBeginFunction = [](){...};  
e.OnThreadEndFunction = [](){...};  
  
// CPU 갯수만큼의 스레드를 가진 thread pool 객체를 만듭니다.  
CThreadPool* p = CThreadPool::Create(&e, GetNoofProcessors());  
// 2개의 스레드를 가지고 싶으면
CThreadPool* p = CThreadPool::Create(&e, 2);

//----------파라메터로 이 thread pool을 등록한 객체를 전달합니다.----------
//서버단의 경우
CStartServerParameter param;
param.m_externalUserWorkerThreadPool = p;

//클라이언트단의 경우
CNetConnectionParam param;
//표 23-2 프라우드넷의 스레드 모델 종류참조
param.m_userWorkerThreadModel = ThreadModel_UseExternalThreadPool;
param.m_externalUserWorkerThreadPool = p;

스레드 풀의 설정 방법은 기본적으로 위와 같으며, 원하시는 Case에 따라 설정값을 달리 해주십시오.

서버 뿐만 아니라 클라이언트에서도 위와 같은 여러가지 스레드 모델을 설정하실 수 있습니다. 자세한 것은 CNetConnectionParam의 도움말을 참조 부탁드립니다.

사례별 시나리오

Case: 여러 네트워크 모듈이 같은 Thread Pool를 공유

그림 23-1서버에서 여러 호스트 모듈이 같은 Thread Pool를 공유하는 예

CStartServerParameter param1;

CStartServerParameter param2; 
CStartServerParameter param3; 

//Proud::CThreadPool::Create()로 thread pool 객체를 명시적으로 생성
CThreadPool* p = CThreadPool::Create(...);  
  
//user worker thread pool을 공유해줍니다
//Networker thread pool은 디폴트 설정이 그대로 유지되어, 공유되지 않습니다.
param1.m_externalUserWorkerThreadPool = p;
param2.m_externalUserWorkerThreadPool = p;  
param3.m_externalUserWorkerThreadPool = p;

Case: Networker, User worker thread pool의 개수를 각각 다르게 사용

CStartServerParameter param;

CThreadPool* p1 = CThreadPool::Create(..., 1); 
CThreadPool* p2 = CThreadPool::Create(..., 4);  
  
param.m_externalNetWorkerThreadPool = p1;  
param.m_externalUserWorkerThreadPool = p2;

Case: Networker와 User worker의 thread pool 통합

CStartServerParameter param;  
CThreadPool* p = CThreadPool::Create(..., 8); 
param.m_externalNetWorkerThreadPool = p;  
param.m_externalUserWorkerThreadPool = p;

Case: 순수 싱글 스레드 모델

순수하게 싱글 스레드로 작동하는 서버를 CPU 갯수만큼 띄우기 위한 방법입니다.

이 때는, 스레드가 전혀 없는 스레드 풀 객체를 만들고 수동으로 thread pool을 숨쉬게 해주는 함수를 지속 호출해줘야 합니다.

Networker thread pool을 지정 시 Process나 FrameMove 등을 너무 느리게 불러주면 안됩니다. callback되는 메소드들은 최대한 빨리 필요한 처리를 완료해야 합니다.

그렇지 않은 경우, 내부에서 ping 측정이 이상하게 나오거나, 심한 경우 서버와 클라이언트 간의 연결이 끊어지는 등의 불상사가 발생할 수 있습니다.

CStartServerParameter param;
CThreadPool* p = CThreadPool::Create(..., 0);  

param.m_externalNetWorkerThreadPool = p;  
param.m_externalUserWorkerThreadPool = p;  
....

void main()  
{  
    while(true)  
    {  
        // 최대 10밀리초까지 기다리면서, thread pool에 쌓인 이벤트를 처리한다.  
        p->Process(10);  
    }  
}

23.2Thread Model 설정하기(클라이언트 전용)

위에서 user worker와 networker를 분리하여 실수가 일어나지 않도록 막았지만, 여전히 FrameMove는 명시적으로 호출해야 한다는 부분이 남아 있습니다. 이는 쌓인 RMI를 원하는 시점에 실행할 수 있다는 장점을 제공합니다.

그러나 클라이언트단의 코드가 복잡하여 Process나 FrameMove를 호출하면 로직이 꼬인다든가, 프로그래머의 실수로 해당 코드가 누락이 됬다던가 하는 상황이 있을 수 있습니다. 그것을 신경쓰지 않아도 되도록 설정하는 방법이 Thread Model 설정입니다.

프라우드넷은 네트워킹에 대해 다양한 조작 방법도 제공하지만, 그것을 신경쓰지 않도록 설정할 수도 있습니다.

표 23-2프라우드넷의 스레드 모델 종류

ThreadModel_SingleThreaded

RMI 및 메세지 콜백이 사용자가 FrameMove를 부를 때 완료됩니다.(기본값)

ThreadModel_MultiThreaded

사용자가 FrameMove를 부르지 않아도 바로 콜백이 완료됩니다.

ThreadModel_UseExternalThreadPool

thread pool 지정 시 반드시 이것으로 설정해 줘야 합니다.

CNetConnectionParam의 m_netWorkerThreadModel과 m_userWorkerThreadModel에서 위의 값을 셋팅할 수 있습니다. 기본값은 ThreadModel_SingleThreaded입니다.

예제 코드는 아래와 같습니다.

CNetConnectionParam param;

//user worker, network 둘 다 사용자 지정 스레드 풀을 사용하고 싶을 경우
param.m_userWorkerThreadModel = ThreadModel_UseExternalThreadPool;  
param.m_netWorkerThreadModel = ThreadModel_UseExternalThreadPool;  

//단순히 FrameMove를 부르고 싶지 않을 경우
param.m_userWorkerThreadModel = ThreadModel_MultiThreaded;