[WINDOWS VIA C/C++] 06.스레드의 기본
https://www.hanbit.co.kr/store/books/look.php?p_code=B2974835990
제프리 리처의 Windows via C/C++(복간판)
이 책은 윈도우 XP, 윈도우 비스타, 윈도우 서버 2008까지 내용을 포괄한다. 이미 윈도우 10이 출시된 지 오래지만 윈도우의 기본 구조는 변하지 않아 아직까지도 이 책은 윈도우 시스템 프로그래
www.hanbit.co.kr
해당 책을 읽고 학습 목적으로 간단하게 정리한 글입니다.
개요
이번 챕터에서는 프로세스와 스레드의 차이, 책임, 스레드의 커널 오브젝트 등에 대해 알아본다.
프로세스와 마찬가지로 스레드 또한 2가지 요소로 구성되어있다.
1. 스레드 컨텍스트(실행 상태): 스레드를 관리하기 위한 목적으로 OS가 사용하는 커널 오브젝트
2. 스레드가 코드를 실행할 때 사용하는 스레드 스택
프로세스는 스스로 실행될 수 없으며 스레드를 통해 실행된다.
이 관점에서는 프로세스는 스레드의 저장소이다.
스레드 간에는 단일 주소 공간을 공유할 수 있다.
커널 오브젝트 핸들도 프로세스마다 유효하기 때문에 스레드 간에는 같은 값을 가진다.
스레드는 본인 커널 오브젝트와 스택 정도만이 필요하기 때문에 프로세스에 비해 가볍다.
스레드를 생성해야 하는 이유
프로그램이 시작되면 프로세스 초기화와 동시에 메인 스레드가 생성된다.
이후 추가적인 스레드 생성이 가능하다.
- 스레드를 사용하면 UI단순화를 실행해볼 수 있다.
- 스레드를 사용하면 확장성이 좋은 구조로 애플리케이션을 설계해볼 수 있다.
OS가 다수의 스레드를 동시에 수행할 수 있기 때문에 하나의 프로세스를 실행하더라도 여러개의 CPU코어가 있다면 병렬적으로 처리할 수 있다.
스레드를 생성하지 말아야 하는 경우
스레드는 유용한 경우가 많지만 특정 경우에는 역효과를 낼 수 있다.
(책에서는 문서를 프린트하며 동시에 수정하는 경우를 설명함)
대부분 애플리케이션에서는 UI를 위한 컴포넌트는 동일한 스레드를 사용해야 한다. (windows ui framework는 대부분 thread-safe하지 않음)
thread를 새로 생성할 때는 신중한 고려가 필요하다.
스레드 함수 만들기
프로세스의 주 스레드의 진입점 함수가 main인것 처럼 새로 만들어지는 스레드는 스레드의 진입점 함수가 필요하다.
Window에서는 아래와 같은 규격의 진입점 함수를 사용한다.
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
스레드는 스레드 진입점 함수가 반환될 때 리소스가 정리된다.
스레드 함수가 반환하는 값은 main의 종료코드와 같이 취급된다.
CreateThread 함수
CreateThread 함수(processthreadsapi.h) - Win32 apps
호출 프로세스의 가상 주소 공간 내에서 실행할 스레드를 만듭니다.
learn.microsoft.com
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out, optional] LPDWORD lpThreadId
);
CreateThread 함수를 통해 thread를 생성하면 thread에 대한 handle이 return된다.
stack크기 인자는 바이트 단위이며 0을 넣으면 기본 스택 크기인 1MB가 할당된다.(설정으로 기본 스택 크기와 reserve, commit크기를 변경할 수 있음)
스레드의 종료
스레드는 4가지 방법으로 종료된다.
1. 스레드 함수 반환
2. 스레드 함수에서 ExitThread 호출
3. TerminateThread 호출
4. 스레드가 포함된 프로세스가 종료됨
1번 방법이 가장 권장된다.
1번 방법
- C++객체들이 적절히 소멸
- OS가 스레드의 스택 메모리 반환
- 시스템이 스레드 종료 코드 설정
- 스레드에 대한 커널 오브젝트의 Usage Count 정상 감소
ExitThread를 호출한다면 OS리소스는 정상 정리되지만, C++객체가 정상 소멸되지 않는다.
TerminateThread는 다른 스레드에서도 호출될 수 있기 때문에 스레드의 코드흐름에 관계없이 종료될 수 있다.
프로세스를 강제 종료하면 내부의 스레드에 대해 TerminateThread를 호출한다.
Thread를 생성할 때 CREATE_SUSPEND 플래그가 설정되었다면 정지 카운트를 1, 없다면 0으로 한다.
C의 기본 런타임 라이브러리는 초기에 thread-safe하지 않았다.

현재는 CRT 함수 자체는 thread-safe하지만 비 thread-safe한 함수들이 여전히 존재하기 때문에 유의하며 코딩해야 한다.
특히 CreateThread대신 _beginthreadex, ExitThread대신 _endthreadex사용이 필요하다.
(현대 UCRT는 CreateThread도 자동초기화 한다고 함)
Process와 동일하게 자신에 대한 핸들은 pseudo핸들이다.(GetCurrentThread()를 통해 얻는)
진짜 핸들이 가지고 싶다면 동일하게 DuplicateHandle을 호출하면 된다.