프로세스와 스레드
- 프로세스
- 가상의주소 공간 + 프로세스 커널 오브젝트(PKO)
- 직접동작하진 않는다 정적인 존재
- 스레드
- 프로세스내에서 코드를 실행하는 실행흐름(동적인 존재)
- 스레드역시 OS입장에선 관리의 대상이기에 TKO(Thread Kernel Object)가 존재
- TKO내에 참조계수, 보안설정, ID 등이 관리된다.
Quantum Time
A B Thred
| |
| -> |
| |
위 그림과 같이 하나의 CPU에서 두 개의 Thread를 동작한다면 A Thread에서 특정 시간 후 B Thread로 점유권을 이전해야할 것이다. 이 때 이전하는 특정 시간을 Quantum Time이라 한다.
Thread를 이전하는 행위 자체는 Context Switch라 하며 이때 말하는 Context의 의미는 아래에 설명.
또한 Thread가 이전 시 직전에 사용 되던 Thread의 정보(실행주소, 스택주소 등)을 Kernel Object에 담고 있어야 하고, 이 정보를 Thread Kernel Object의 CONTEXT에 저장하며 저장되는 정보는
- EIP 레지스터 정보 : 실행주소
- ESP 레지스터 정보 : 스택주소
를 대표적으로 저장하게 된다.
Thread 생성
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
DWORD __stdcall foo(void* p)
{
return 0;
}
int _tmain()
{
DWORD tid;
HANDLE handle = CreateThread(0, 0, foo, 0, 0, &tid);
getchar();
CloseHandle(handle);
}
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // Kernel Object의 보안속성
SIZE_T dwStackSize, // Kernel Object의 Stack Size(기본은 1M)
LPTHREAD_START_ROUTINE lpStartAddress, // 실행 함수 주소
__drv_aliasesMem LPVOID lpParameter, // 매개변수
DWORD dwCreationFlags, // Thread 플래그
LPDWORD lpThreadId // Thread ID
);
- 모든 Thread는 생성 시 참조 개수가 항상 2개다(자기자신, 부모 Thread)
- 따라서 부모에서
CloseHandle
해줘야 된다.- 만약 해주지 않으면 쓰레드가 남아있게 된다.
Thread의 문제점은?
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
DWORD __stdcall foo(void* p)
{
// 여기서 C 표준 함수 사용시 문제가 발생할 수 있다.
// 이런 문제의 해결을 위해 _beginthreadex를 쓰는것을 추천한다
// 자세한설명은 이후에 _beginthreadex를 설명하며 하겠음
return 0;
}
int _tmain()
{
DWORD tid;
HANDLE handle = CreateThread(0, 0, foo, 0, 0, &tid);
getchar();
CloseHandle(handle);
}
해결책?
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#include <process.h>
// 순수 c의 자료형으로 리턴받는다
unsigned int __stdcall foo(void* p)
{
// C함수를 사용하더라도 메모리 누수가 없다.
return 0;
}
int _tmain()
{
unsigned int tid;
HANDLE handle = (HANDLE)_beginthreadex_(0, 0, foo, 0, 0, &tid);
getchar();
CloseHandle(handle);
}
Thread 종료
#include <stdio.h>
#include <Windows.h>
#include <process.h>
struct Test
{
Test() { printf("Test()\n"); }
~Test() { printf("~Test()\n"); }
};
UINT __stdcall foo(void* p)
{
Test t;
_endthreadex(0);
ExitThread(0); // 이렇게 Thread를 종료할 수있다.
// 단, 이렇게 종료시 Test의 소멸자가 호출되지 않음.
return 0;
}
int main()
{
HANDLE hThread = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);
TerminateThread(hTread, 200); // 외부에서 호출가능,
// 이것도 역시 내부 오브젝트(Test)의 소멸자가 호출되지 않는다.
getchar();
}
#include <stdio.h>
#include <Windows.h>
#include <process.h>
UINT __stdcall foo(void* p)
{
_tprintf(_T("start foo\n"));
Sleep(5000);
_tprintf(_T("end foo\n"));
return 0;
}
int main()
{
HANDLE hThread = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);
return 0; // 바로종료?
// 주 스레드 main 함수가 return 될 시 프로세스 내 모든 스레드가 강제 종료 된다.
ExitThread(0); // 메인스레드만 죽인다, 생성된 스레드는 살아있음.
}
새로만든 Thread를 대기하게 변경해보자.
int main()
{
HANDLE hThread = (HANDLE)_beginthreadex(0, 0, foo, 0, 0, 0);
DWORD ret = WaitForSingleObject(hThread, INFINITE);
if(ret == WAIT_OBJECT_0)
{
DWORD code;
GetExitCodeThread(hTread, &code); // 종료코드를 받는다
}
else if (ret == WAIT_TIMEOUT)
{
}
return 0;
}