2015년 1월 8일 목요일

TCP/IP 소켓 프로그래밍 19강

어느덧 파트3까지 왔다. 파트 3부터는 윈도우 기반 프로그래밍이다.
19강은 윈도우에서의 쓰레드 사용이다.

19-1 : 커널 오브젝트


커널 오브젝트란???
운영체제가 만드는 리소스의 종류는 다양하다.
이런 리소스를 관리하는 방식도 다양하다.
리소스마다 유지해야 하는 정보가 다르니,
이 데이터 블록 형태는 리소스마다 차이가 있다.
이 데이터 블록을 커널 오브젝트라고 한다.

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

19-2 : 윈도우 기반의 쓰레드 생성


프로세스와 쓰레드의 관계

main 함수의 호출주체는 쓰레드다.
각종 운영체제들도 운영체제 레벨에서 쓰레드를 지원한다.
따라서 쓰레드를 별도로 생성하지 않는 프로그램은 "단일 쓰레드 모델의 프로그램"
그리고 쓰레드를 별도로 생성하는 방식의 프로그램은 "멀티 쓰레드 모델의 프로그램"
이라고 한다.

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

1. 이 함수가 호출되면
2. 쓰레드가 생성되고
3. os는 관리를 위해 커널 오브젝트를 생성한다
4. 이 커널 오브젝트의 구분자 역할을 하는, 정수로 표현되는 "핸들"을 반환한다.

#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의 저장을 위한 변수의 주소 값 전달.

뭔가 굉장히 복잡해 보이는데 lpStartAddress , lpParameter 만 신경 쓰고,
나머지는 0 또는 NULL을 전달하면 된다.

C/C++ 표준함수를 호출하려면 CreateThread 함수호출을 사용하면 안된다.
왜냐하면 안정적으로 동작하지 않기 때문이다.
쓰레드에 안전한 C 표준함수의 호출을 위한 쓰레드 생성 함수를 소개한다.

#include <process.h>
uintptr_t _beginthreadex (
           void *security,
           unsigned stack_size,
           unsigned (*start_address)(void*),
           void *arglist,
           unsigned initflag,
           unsigned *thrdaddr
);
-> 성공시 쓰레드 핸들, 실패시 0반환

CreateThread 함수와 비교해 보면, 거의 비슷하다.
매개변수의 수도 같고, 의미와 순서도 같다. 단지 이름과 자료형만 조금 다를 뿐이다.
참고로 uintptr_t 는 64비트로 표현되는 unsigned 정수 자료형이다.

소스를 보자.

#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;
//쓰레드의 main 함수로  ThreadFunc를, 그리고 ThreadFunc에 변수 param의 주소값이 전달 하면서 쓰레드의 생성을 요구하고 있다.
hThread=(HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)&param, 0, &threadID);
if(hThread==0)
{
puts("_beginthreadex() error");
return -1;
}
//3초간의 대기시간을 갖는다.
Sleep(3000);
puts("end of main");
return 0;
}

//WINAPI 는 _beginthreadex  함수가 요구하는 호출규약을 지키기 위해 삽입한 것이다.
unsigned WINAPI ThreadFunc(void *arg)
{
int i;
int cnt=*((int*)arg);
for(i=0; i<cnt; i++)
{
Sleep(1000);  puts("running thread");  
}
return 0;
}

main 함수의 반환으로 인해 프로세스가 종료되면, 그 안에 담겨 있는 모든 쓰레드들도 함께 종료된다.

19-3 : 커널 오브젝트의 두 가지 상태


커널 오브젝트의 상태, 그리고 상태의 확인을 운영체제가 하는데
커널 오브젝트의 signaled, non-signaled  상태는 boolean 형 변수 하나로 표현한다.

이 때 쓰는 함수를 보자.

#include <windows.h>
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
-> 성공시 이벤트 정보, 실패 시 WAIT_FAILED 반환
-hHandle : 상태 확인의 대상이 되는 커널 오브젝트의 핸들을 전달.
-dwMilliseconds : 1/1000초 단위로 타임아웃을 지정, 인자로 INFINITE 전달시, 커널 오브젝트가 signaled 상태가 되기 전에는 반환하지 않는다.
-반환값 : signaled 상태로 인한 반환시, WAIT_OBJECT_0 반환, 타임아웃으로 인한 반환시 WAIT_TIMEOUT 반환.

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

다음 함수는 둘 이상의 커널 오브젝트를 대상으로 상태를 확인하는 경우에 필요한 함수다.

#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 상태가 되기 전에는 반환하지 않는다. 

새벽 3시 넘어서 글을 쓰다 보니 졸려서 비몽사몽이다 ㅡㅡ;;
윈도우 함수들은 왜이리 이상한지 모르겠다.

역시 예제를 통해서 확인해보는 것이 좋다.
소스 다운 받기

댓글 없음:

댓글 쓰기