IOCP 공부 노트: (1) _beginthreadex , CreateThread

게임 서버 프로그래밍/네트워크 개념정리

2020. 7. 10. 23:15

 _beginthreadex, CreateThread의 공통된 역할

 

 두 함수의 목적은 Thread를 생성하는 것. 들어가는 파라미터의 대부분이 같고 반환 형식이 다르다.

 

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES   lpThreadAttributes, // 보안특성 지정 (nullptr)
  SIZE_T                  dwStackSize, // 스택 사이즈 (0으로 두면 기본 크기 사용)
  LPTHREAD_START_ROUTINE  lpStartAddress, // 스레드가 실행할 함수 포인터
  __drv_aliasesMem LPVOID lpParameter, // 스레드에 전달 될 파라미터
  DWORD                   dwCreationFlags, // 스레드 생성 제어 플래그
  LPDWORD                 lpThreadId // 스레드 식별자를 받는 변수에 대한 포인터 (0)
);
uintptr_t _beginthreadex( // NATIVE CODE
   void *security,
   unsigned stack_size,
   unsigned ( __stdcall *start_address )( void * ),
   void *arglist,
   unsigned initflag,
   unsigned *thrdaddr
);

_beginthreadex 같은 경우 타입을 제외하고는 CreateThread 파라미터와 일치하게 넣어준다.

 

여기서 중요한 것은 파라미터의 타입이 다른 부분이다. 함수 포인터를 넣어주는 부분에 대해서

 

CreateThread 같은 경우에는 DWORD 값을 가지지만, _beginthreadex 같은 경우에는 UINT 값을 가진다.

 

따라서 기존에 함수가 DWORD 형식으로 정의되어 있다면 (UINT)로 캐스팅을 해준다.

 

또한, 반환되는 타입도 다르다. 

 

CreateThread 같은 경우에는 HANDLE 값을 반환하지만 _beginthreadex 같은 경우에는 UINT 포인터를

 

반환한다. 따라서 (HANDLE)로 캐스팅을 해 주어야 한다.

int threadcount = sysinfo.dwNumberOfProcessors * 2;
	HANDLE* pHandle = new HANDLE();
	UINT dwThreadId;
	for (int i = 0; i < threadcount; i++) {
		HANDLE hThread = CreateThread(nullptr, 0, makeThread, (LPVOID)i, 0, 0);
	}
int threadcount = sysinfo.dwNumberOfProcessors * 2;
	HANDLE* pHandle = new HANDLE();
	UINT dwThreadId;
	for (int i = 0; i < threadcount; i++) {
		HANDLE hThread = (HANDLE)_beginthreadex(nullptr, 0, makeThread,(LPVOID)i, 0, &dwThreadId);
	}

[여기서 makeThread의 반환 타입은 각각 DWORD,UINT 이다]

 

 언제 무엇을 사용해야 할까?

 

 결론부터 말하자면 _beginthreadex()를 사용하자.

 

이유는 다음과 같다.

 

1. CreateThread()는 멀티스레드가 고려되지 않은 시기에 만들어졌다. 따라서, 오류 발생 가능성을 내포한다.

2. _beginthreadex()는 전역 자원을 사용하는 경우(strtok,errono..) 각각의 전역 자원공간을 스레드마다 할당해준다.

   따라서 다른 스레드들로부터 영향을 받지 않도록 자신이 호출한 스레드의 데이터 블록에만 접근 가능하다.

3. 스레드가 생성되었을 때 어떻게 새로운 데이터 블록을 할당해야 할지 운영체제는 알지 못한다. 시스템이 C/C++로 

   개발되었는지, 멀티스레드 환경에서 안전한 함수가 호출되었는지에 대해 전혀 알지 못한다.

   따라서, 운영체제가 아닌 C/C++ 런타임 라이브러리가 제공하고 있는 _beginthreadex 함수를 호출해야 한다.

 

그 외에도 메모리 누수같은 문제도 있다. 앞서 말했듯이 결론은 _begintrheadex()를 사용하는 것이다.

 

Thread의 개수

 

여기서 Thread의 개수를 CPU 개수 * 2로 지정했다. 이는 내가 임의로 설정한 값이아니라 Microsoft사에서 권장하는 스레드 개수인데.. CPU개수보다 많은 Thread 수를 권장하는 것은 확실하다. 이렇게 잡아둔 이유는 Thread가 Suspend 될 때를 대비해서인데 예를 들어보자. CPU개수와 Thread 개수가 같을 때 (2개로 가정) I/O Completion Queue에 I/O 완료통지가 4개와서 Thread가 2개의 일감을 들고 Release Thread List로 들어가 작업을 하던 도중 Thread 하나가 Suspend 되어 Pause Thread List로 보내졌을 때 CPU 개수와 Thread 개수가 같다면 I/O 완료된 작업이 2개가 남아 있지만 Release Thread List에 남아있는 Thread가 일을 끝낼 때까지 추가적인 작업이 불가능하다. 이럴 때를 대비해서 CPU 수의 2배로 Thread의 개수를 설정해두면 남은 Thread중 하나가 (나중에 들어온 Thread) 일감을 들고 Release Thread List에 들어가 작업을 할 것이다.

 

만약 Paused Thread List에 들어간 Thread가 다시 작업을 시작한다면 현재 Release Thread List에 두 작업이 돌아가고 있기 때문에 복귀가 불가능하다.

 

* Release Thread List에 동시에 돌아갈 수 있는 Thread 개수는 보통 CPU 개수와 같다! (컨텍스트 스위칭을 줄이기위해)

  다음 포스팅에서 보게 될 CreateCompletionPort에서 이를 지정할 수 있다.

 

 

 

'게임 서버 프로그래밍 > 네트워크 개념정리' 카테고리의 다른 글

IOCP 공부노트: (3) IOCP Echo Server  (0) 2020.07.15
IOCP 공부노트: (2) IOCompletionPort  (0) 2020.07.13
객체 리플리케이션  (0) 2020.03.02
압축  (0) 2020.02.24
직렬화  (0) 2020.02.24