책정리/열혈 TCP,IP

19장 Windows에서의 쓰레드 사용

GONII 2015. 8. 5. 16:18
  • 커널 오브젝트(Kernel Objects)

    윈도우 기반에서 쓰레드를 이해하기 위해서는 '커널 오브젝트'를 먼저 이해해야 한다.

    커널 오브젝트란 무엇인가?

    프로세스, 쓰레드, 파일, 그리고 앞으로 이야기할 세마포어, 뮤텍스 등 운영체제가 만드는 리소스(Resource)의 종류는 다양하다. 그리고 이들 대부분은 프로그래머의 요청에 의해서 생성되며, 요청의 방식도 제 각각이다. 그런데 이러한 차이점에도 불구하고 이들은 다음의 공통점을 지닌다

    "윈도우 운영체제가 생성해서 관리하는 리소스들이다."

    '관리'의 방식 역시 리소스의 종류에 따라서 차이가 있다. 예를 들어 파일이라면 파일과 관련된 데이터의 입출력 위치, 파일의 오픈 모드(read or write) 등이 등록 및 갱신 되어야 하며, 쓰레드라면 쓰레드의 ID, 그리고 쓰레드가 속한 프로세스의 정보가 등록 및 유지되어야 한다. 이렇듯 운영체제에 의해서 생성되는 리소스들은 관리를 목적으로 정보를 기록하기 위해 내부적으로 데이터 블록을 생성한다. 리소스마다 유지해야 하는 정보가 다르니 이 데이터 블록의 형태는 리소스마다 차이가 있다. 그리고 이 데이터 블록을 가리켜 '커널 오브젝트'라 한다.

    커널 오브젝트의 소유자는 운영체제이다.

    쓰레드, 파일 등의 생성요청이 프로세스 내에서 이뤄지기 때문에, 이 때 생성되는 커널 오브젝트의 소유자는 프로세스라고 생각하기 쉽다. 그러나 커널 오브젝트의 소유자는 커널(운영체제)이다. 그리고 소유자가 커널이라는 것은 다음의 의미를 갖는다

    "커널 오브젝트의 생성, 관리 그리고 소멸시점을 결정하는 것까지 모두 운영체제의 몫이다"

    이렇듯 커널 오브젝트는 생성, 소유의 주체가 운영체제인 데이터 블록이다.

  • 윈도우 기반의 쓰레드 생성

    프로세스와 쓰레드의 관계

    main 함수의 실행 역시도 쓰레드를 기반으로 한다. 즉, 프로세스는 쓰레드를 담는 바구니에 비유된다.

    윈도우에서의 쓰레드 생성방법

    아래 함수가 호출되면 쓰레드가 생성되고, 운영체제는 이의 관리를 위해서 커널 오브젝트도 함께 생성한다. 그리고 이 커널 오브젝트의 구분자 역할을 하는, 정수로 표현되는 '핸들(Handle)'을 반환한다.

#include <windows.h>
 

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,

SIZE_T dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId

);

// 성공 : 쓰레드 핸들 반환

// 실패 : NULL 반환

  • lpThreadAttributes

    쓰레드의 보안관련 정보 전달, 디폴트 보안설정을 위해 NULL 전달

  • dwStackSize

    쓰레드에게 할당할 스택의 크기를 전달, 0전달 시 디폴트 크기의 스택 생성

  • lpStartAddress

    쓰레드의 main 함수정보 전달

  • lpParameter

    쓰레드의 main 함수호출 시 전달할 인자정보 전달

  • dwCreationFlags

    쓰레드 생성 이후의 행동을 결정, 0을 전달하면 생성과 동시에 실행 가능

  • lpThreadId

    쓰레드 ID의 저장을 위한 변수의 주소 값 전달

쓰레드에 안전한 C 표준 함수의 호출을 위한 쓰레드 생성

생성된 쓰레드를 통해서 C/C++ 표준함수를 호출하려면 다음함수를 이용해서 쓰레드를 생성해야 한다. 왜냐하면 CreateThread 함수호출을 통해서 생성되는 쓰레드는 C/C++ 표준함수에 대해서 안정적으로 동작하지 않기 때문이다.

#include <process.h>

uintptr_t _beginthreadex(

void* _Security,

unsigned _StackSize,

unsigned (*_StartAddress)(void *),

void* _ArgList,

unsigned _InitFlag,

unsigned * _ThrdAddr

);
// 생성 : 쓰레드 핸들
// 실패 : 0 반환

매개변수의 의미와 순서는 CreateThread함수와 동일하다.

  • 예제 thread1_win.c

#include <stdio.h>

#include <windows.h>

#include <process.h> // _beginthreadex, _endthreadex

   

unsigned WINAPI threadFunc(void* arg);

   

int main(int argc, char* argv[])

{

HANDLE hThread;

unsigned threadID;

int param = 5;

   

hThread = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (void*)&param, 0, &threadID);

if( hThread == 0 )

{

puts("_beginthreadex() error");

return -1;

}

   

Sleep(3000);

puts("end of main");

   

return 0;

}

unsigned WINAPI threadFunc(void* arg)

{

int i;

int cnt = *((int*)arg);

   

for( i = 0 ; i < cnt ; i++ )

{

Sleep(1000);

puts("running thread");

}

return 0;

}

  • 커널 오브젝트의 두 가지 상태

    커널 오브젝트에는 해당 리소스의 성격에 따라 많은 정보가 담긴다. 그런데 이 중에서도 프로그램의 구현에 있어서 특히 더 관심을 둬야 하는 정보에 대해 '상태(state)'라는 것을 부여한다. 예를 들어서 쓰레드의 커널 오브젝트에 있어서의 큰 관심사는 종료여부이다. 때문에 종료된 상태를 가리켜 'signaled 상태'라고 하고 종료되지 않은 상태를 가리켜 'non-signaled상태'라 한다.

    커널 오브젝트의 상태, 그리고 상태의 확인

    운영체제는 관심사에 대한 정보를 커널 오브젝트에 기록해 둔다. 그리고 운영체제는 우리에게 다음과 같이 약속하고 있다.

    "프로세스나 쓰레드가 종료되면 해당 커널 오브젝트를 signaled 상태로 변경해 놓겠다"

    이 얘기는 프로세스와 쓰레드의 커널 오브젝트 상태가 초기에는 non-signaled 상태라는 뜻도 된다.

    커널 오브젝트는 boolean형 변수 하나를 지니고 있으면서 이의 초기값을 FALSE로 두고 이 상태를 가리켜 non-signaled 상태라 하는 것이다. 반면 약속된 상황이 발생했을 때(이를 두고, '이벤트가 발생했다'라고 표현한다.) 이 값을 TRUE로 두고, 이 상태를 signaled 상태라 하는 것이다. 커널 오브젝트의 성격에 따라 signaled 상태가 되는 상황이 다르다

    WaitForSingleObject & WaitForMultipleObjects

    WaitForSignleObject함수는 하나의 커널 오브젝트에 대해서 signaled 상태인지 확인하기 위해 호출하는 함수이다. WaitForMultipleObjects 함수는 둘 이상의 커널 오브젝트를 대상으로 상태를 확인하는 경우 호출하는 함수이다.

#include <windows.h>

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
// 성공 : 이벤트 정보
// 실패 : WAIT_FAILED

  • hHandle

    상태확인의 대상이 되는 커널 오브젝트의 핸들을 전달

  • dwMilliseconds

    1/1000초 단위의 타임아웃을 지정, 인자로 INFINITE 전달 시, 커널 오브젝트가 signaled 상태가 되기 전에는 반환하지 않는다.

  • 반환 값

    signaled 상태로 인한 반환 시, WAIT_OBJECT_0 반환, 타임아웃으로 인한 반환 시 WAIT_TIMEOUT 반환

#include <windows.h>

 

DWORD WaitForMultipleObjects(

DWORD nCount,

CONST HANDLE *lpHandles,

BOOL bWaitAll,

DWORD dwMilliseconds

);
// 성공 : 이벤트 정보
// 실패 : WAIT_FAILED 반환

  • nCount

    검사할 커널 오브젝트의 수 전달

  • lpHandles

    핸들정보를 담고 있는 배열의 주소 값 전달

  • bWaitAll

    TRUE 잔달 시, 모든 검사대상이 signaled 상태가 되어야 반환
    FALSE 전달 시, 검사대상 중 하나라도 signaled 상태가 되어야 반환

  • dwMilliseconds

    1/1000 초 단위로 타임아웃 지정,

    INFINITE 전달 시 커널 오브젝트가 signaled 상태가 되기 전에는 반환하지 않는다.

위 함수는 이벤트 발생에 의해서 반환되면, 해당 커널 오브젝트를 다시 non-signaled 상태로 되돌리기도 한다. 그리고 이렇게 다시 non-signaled 상태가 되는 커널 오브젝트를 가리켜 'auto-reset 모드' 커널 오브젝트라 하고, 자동으로 non-signaled 상태가 되지 않는 커널 오브젝트를 가리켜 'manual-reset 모드' 커널 오브젝트라 한다.

  • 예제 thread2_win.c

#include <stdio.h>

#include <windows.h>

#include <process.h> // _beginthreadex, _endthreadex

unsigned WINAPI threadFunc(void* arg);

   

int main( int argc, char* argv[])

{

HANDLE hThread;

DWORD wr;

unsigned threadID;

int param = 5;

   

hThread = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (void*)&param, 0, &threadID);

if( hThread == 0 )

{

puts("_beginthreadex() error");

return -1;

}

   

if( (wr = WaitForSingleObject(hThread, INFINITE) ) == WAIT_FAILED)

{

puts("thread wait error");

return -1;

}

   

printf("wait result: %s\n", (wr==WAIT_OBJECT_0) ? "signaled" : "time-out");

puts("end of main");

return 0;

}

   

unsigned WINAPI threadFunc(void* arg)

{

int i;

int cnt = *((int*)arg);

   

for( i = 0 ; i < cnt ; i++ )

{

Sleep(1000);

puts("running thread");

}

return 0;

}

  • 예제 thread3_win.c

#include <stdio.h>

#include <windows.h>

#include <process.h>

   

#define NUM_THREAD 50

unsigned WINAPI threadInc(void* arg);

unsigned WINAPI threadDes(void* arg);

long long num = 0;

   

int main(int argc, char* argv[])

{

HANDLE tHandles[NUM_THREAD];

int i;

   

printf("sizeof long long: %d\n", sizeof(long long));

for( i = 0 ; i < NUM_THREAD ; i++ )

{

if( i % 2 )

tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);

else

tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);

}

WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);

printf("result : %11d\n", num);

return 0;

}

unsigned WINAPI threadInc(void* arg)

{

int i;

for( i = 0 ; i < 50000 ; i++ )

num += 1;

   

return 0;

}

unsigned WINAPI threadDes(void* arg)

{

int i;

for( i = 0 ; i < 50000 ; i++ )

num -= 1;

   

return 0;

}

 

반응형