개념
스레드 란? CPU의 1개당 실행단위 를 이야기한다.
한개의 프로세스(프로그램... 이라고 표현하면 잘못된 것 이긴 하지만, 대략적으로) 마다 최소 1개 이상의 Thread를 가질 수 있다.
작업을 시작하려면 작업공간과 인부를 가져야 한다고 이해하자. 여기서 작업공간은 메모리(램또는 SSD같은것) 이고,
인부는 CPU라고 생각하면 된다.
정리하면 다음과 같다.
- 한 Process는 최소 1개 이상의 Thread를 갖는다.( Memory 와 함께 )
- 즉, 한 Process는 [ CPU + Memory ] ( VMS 버추얼 메모리 ) 를 가진다.
- Thread는 개별화된 흐름(문맥)이다.
- int main()함수가 n개(스레드 개수만큼) 있다고 생각하면 된다.
- 전용 스택을 갖는 실행의 단위이다.
- 1MB가량의 콜스택 메모리를 가진다. 입력, 반환되는 최대단위를 말함.
- 모든 Thread는 자신이 속한 Process의 가상 메모리 공간을 공유한다.
- 자신이 속한 프로세스에 속한 메모리에 항상 별도의 권한허가 없이 접근할 수 있다.
스레드 상태값
Run으로 실행되었을 때, 스레드의 내부함수 값에 따라 Suspended상태에서 Resum으로 돌아가. Run으로 돌아가게 진행되는데,
이 Suspended와 Resum을 합쳐서 생각해도 되는것이 Sleep과 Alertable Wait이다.
Alertable Wait은, Sleep상태를 외부에서 사용하기 위해 깨우기 위해 사용하는 다른종류의 Sleep이라고 이해하면 된다.
다중 스레드의 동작원리 예시
MainProcess가 실행되면, CraeteThread()가 동시에 실행된다.
- Run으로 T1이 실행됨.
- T1 에서 CraeteThread()가 실행. T2 로 분리됨
- T2에서 Event가 종료될 때 까지. T1에서는 Wait에서 대기.
- T2에서 Event가 반환되고, End로 소멸. T1은 진행
이러한, 데이터의 인풋 아웃풋은 보통 Queue로 진입을 제어하게 된다.
그러나, 스레드가 n개 이상일 때, 동시에 같은 메모리 영역에 접근하려고 진행할 경우,
레이스컨디션 문제가 발생하게 된다.
이러한 문제를 방지하기 위해 Creitical Section을 사용해서 병목지점을 통제할수 있다.
또는 시점으로 관리할 수 있다.
(T1에서 T2의 Event를 감지해서, 감지되었을 경우 Wait으로 대기해주는 방식으로 처리할 수도 있다.)
스레드 동기화
위에서 말한 레이스 컨디션을 방지하기 위한 방법이다.
임계구간 코드가 여러 스레드에서 동시에 실행되는 일을 막는 것
간단한 예시를 통해 알아본다.
DWORD dwThreadID = 0;
// 새로운 스레드를 생성.
HANDLE hThread = ::CreateThread(
NULL, // 보안속성은 상속
0, // 스택 메모리는 기본크기(1MB)
ThreadFunction, // 스레드로 실행할 함수이름.
NULL, // 함수에 전달할 매개변수.
0, // 생성 플래그는 기본값 사용.
&dwThreadID); // 생성된 스레드 ID 저장.
// 작업자 스레드 생성 API
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreateionFlags,
[out, optional] LPDWORD lpThreadID
);
스레드의 작업이 종료된것을 확인하려면, hEvent객체를 생성해서 이벤트 핸들을 스레드 함수에 전달해야 한다.
//이벤트 객체를 생성한다.
HANDLE hEvent = ::CreateEvent(
NULL, //디폴트 보안 속성 적용.
FALSE, //자동으로 상태 전환.
FALSE, //초기상태는 FALSE.
NULL); //이름 없음.
CreateEventW(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_ BOOL bManualReset,
_In_ BOOL bInitialState,
_In_opt_ LPCWSTR lpName
);
위와같이 이벤트 객체를 생성해서, 다음과 같이 스레드를 실행할 때 전달한다.
HANDLE hThread = ::CreateThread(NULL, 0,
ThreadFunction,
hEvent, //이벤트 핸들을 스레드 함수에 전달한다.
0, &dwThreadID);
이러한 스레드의 이벤트를 다음과 같이 검사할 수 있다.
if (::WaitForSingleObject(hEvent, INFINITE) == WAIT_OBJECT_0)
{
puts("종료 이벤트를 감지했습니다!");
::CloseHandle(hEvent);
hEvent = NULL;
}
//WAIT_OBJECT_0 (0x00000000L):
//지정된 개체가 신호 상태가 되어 대기에서 벗어난 경우 반환됩니다.
//WAIT_ABANDONED (0x00000080L):
//대기 중인 쓰레드가 소유한 뮤텍스 개체가 소유권이 해제되지 않은 상태로 소멸된 경우 반환됩니다. 이 반환 값은 주로 뮤텍스에 대한 대기에서 발생합니다.
//WAIT_TIMEOUT (0x00000102L):
//대기 시간이 만료되어 대기에서 벗어난 경우 반환됩니다. 이 경우 dwMilliseconds 파라미터가 0이 아니어야 합니다.
//WAIT_FAILED ((DWORD)0xFFFFFFFF):
//함수가 실패한 경우 반환됩니다. GetLastError 함수를 호출하여 구체적인 오류 코드를 가져올 수 있습니다.