스레드 개요 [Windows System Programming]

다루는 내용

스레드는 하나의 프로세스 내에서 실행하는 작업 단위입니다.

Windows 응용 프로세스의 진입점(main 혹은 WinMain 등)을 수행하는 것은 Main 스레드입니다.

그리고 별도의 진입점으로 시작하는 스레드를 생성할 수 있는데 이 때 사용하는 함수가 CreateTherad입니다.

여기에서는 CreateThread 함수를 알아볼게요.

그리고 스레드를 사용하지 않은 코드와 사용한 코드의 동작을 비교해 봅시다.

1. CreateThread
2. 스레드를 사용하지 않은 코드 VS 사용한 코드

1. CreateThread

HANDLE
WINAPI
CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    SIZE_T dwStackSize,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    LPDWORD lpThreadId
    );                                           msdn 바로가기

반환 값

성공하면 스레드 개체 핸들을 반환합니다.

실패하면 NULL을 반환합니다.

LPSECURITY_ATTRIBUTES lpThreadAttributes

보안 설명자입니다.

NULL을 전달하면 기본 보안 설명자를 사용합니다.

SIZE_T dwStackSize

스레드의 스택 크기입니다. 스레드는 독립적인 작업 흐름으로 스택 메모리를 독립적으로 사용합니다.

0을 전달하면 기본 크기를 사용합니다.

LPTHREAD_START_ROUTINE lpStartAddress

스레드 진입점 함수를 전달합니다.

스레드 진입점 함수는 다음과 같은 시그니쳐를 갖습니다.

DWORD WINAPI ThreadProc(LPVOID lpParameter);

LPVOID lpParameter

스레드 진입점에 전달할 파라미터입니다.

DWORD dwCreationFlags

생성 플래그입니다.

0을 전달하면 스레드는 CreateThread 함수 호출 직후에 빠르게 스레드 진입점을 수행합니다.

CREATE_SUSPENDED를 전달하면 일시 중단 상태로 시작합니다. ResumeThread 함수를 호출하여 스레드를 재개할 수 있습니다.

#define DEBUG_PROCESS                     0x00000001
#define DEBUG_ONLY_THIS_PROCESS           0x00000002
#define CREATE_SUSPENDED                  0x00000004
#define DETACHED_PROCESS                  0x00000008

#define CREATE_NEW_CONSOLE                0x00000010
#define NORMAL_PRIORITY_CLASS             0x00000020
#define IDLE_PRIORITY_CLASS               0x00000040
#define HIGH_PRIORITY_CLASS               0x00000080

#define REALTIME_PRIORITY_CLASS           0x00000100
#define CREATE_NEW_PROCESS_GROUP          0x00000200
#define CREATE_UNICODE_ENVIRONMENT        0x00000400
#define CREATE_SEPARATE_WOW_VDM           0x00000800

#define CREATE_SHARED_WOW_VDM             0x00001000
#define CREATE_FORCEDOS                   0x00002000
#define BELOW_NORMAL_PRIORITY_CLASS       0x00004000
#define ABOVE_NORMAL_PRIORITY_CLASS       0x00008000

#define INHERIT_PARENT_AFFINITY           0x00010000
#define INHERIT_CALLER_PRIORITY           0x00020000    // Deprecated
#define CREATE_PROTECTED_PROCESS          0x00040000
#define EXTENDED_STARTUPINFO_PRESENT      0x00080000

#define PROCESS_MODE_BACKGROUND_BEGIN     0x00100000
#define PROCESS_MODE_BACKGROUND_END       0x00200000
#define CREATE_SECURE_PROCESS             0x00400000

#define CREATE_BREAKAWAY_FROM_JOB         0x01000000
#define CREATE_PRESERVE_CODE_AUTHZ_LEVEL  0x02000000
#define CREATE_DEFAULT_ERROR_MODE         0x04000000
#define CREATE_NO_WINDOW                  0x08000000

#define PROFILE_USER                      0x10000000
#define PROFILE_KERNEL                    0x20000000
#define PROFILE_SERVER                    0x40000000
#define CREATE_IGNORE_SYSTEM_DEFAULT      0x80000000
#define STACK_SIZE_PARAM_IS_A_RESERVATION   0x00010000

LPDWORD lpThreadId

스레드 식별자를 수신하는 변수의 주소입니다.

수신할 필요가 없으면 NULL을 전달하세요.

2. 스레드를 사용하지 않은 코드 VS 사용한 코드

단순한 작업을 반복하는 함수를 호출하는 예제를 통해 스레드의 동작을 살펴보고자 합니다.

스레드를 사용하지 않은 코드

콘솔 응용 프로그램을 작성합니다.

DoIt 함수는 정수형 id를 입력 인자로 받아 반복문을 20번 수행합니다.

반복문에서는 id와 반복 횟수를 출력하고 0.1 미만의 랜덤한 시간동안 멈춥니다.

main 진입점에서는 DoIt 함수에 1을 전달하여 호출합니다.

그리고 DoIt 함수에 2를 전달하여 호출합니다.

그리고 반복문을 30회 수행하며 수행 횟수를 출력 및 0.1초 미만의 랜덤한 시간동안 멈춥니다.

#include <Windows.h>
#include <stdio.h>
void DoIt(int id)
{
	printf("ID:%d\n", id);
	for (int i = 0; i < 20; i++)
	{
		printf("DoIt %d,%d\n", id, i);
		Sleep(rand() % 100);
	}	
}
int main()
{
	printf("메인\n");
	DoIt(1);
        DoIt(2);

	for (int i = 0; i < 30; i++)
	{
		printf("메인 쓰레드:%d\n", i);
		Sleep(rand() % 100);
	}
	return 0;
}

실행 결과

실행 결과를 보면 DoIt(1)을 모두 수행한 후에 DoIt(2)를 모두 수행한 후에 main 함수의 반복문을 수행하는 것을 볼 수 있습니다.

만약 이 세 개의 작업을 독립적으로 수행하려면 어떻게 해야 할까요?

이 때 스레드가 필요합니다.

메인
ID:1
DoIt 1,0
DoIt 1,1
DoIt 1,2
DoIt 1,3
DoIt 1,4
DoIt 1,5
DoIt 1,6
DoIt 1,7
DoIt 1,8
DoIt 1,9
DoIt 1,10
DoIt 1,11
DoIt 1,12
DoIt 1,13
DoIt 1,14
DoIt 1,15
DoIt 1,16
DoIt 1,17
DoIt 1,18
DoIt 1,19
ID:2
DoIt 2,0
DoIt 2,1
DoIt 2,2
DoIt 2,3
DoIt 2,4
DoIt 2,5
DoIt 2,6
DoIt 2,7
DoIt 2,8
DoIt 2,9
DoIt 2,10
DoIt 2,11
DoIt 2,12
DoIt 2,13
DoIt 2,14
DoIt 2,15
DoIt 2,16
DoIt 2,17
DoIt 2,18
DoIt 2,19
메인 쓰레드:0
메인 쓰레드:1
메인 쓰레드:2
메인 쓰레드:3
메인 쓰레드:4
메인 쓰레드:5
메인 쓰레드:6
메인 쓰레드:7
메인 쓰레드:8
메인 쓰레드:9
메인 쓰레드:10
메인 쓰레드:11
메인 쓰레드:12
메인 쓰레드:13
메인 쓰레드:14
메인 쓰레드:15
메인 쓰레드:16
메인 쓰레드:17
메인 쓰레드:18
메인 쓰레드:19
메인 쓰레드:20
메인 쓰레드:21
메인 쓰레드:22
메인 쓰레드:23
메인 쓰레드:24
메인 쓰레드:25
메인 쓰레드:26
메인 쓰레드:27
메인 쓰레드:28
메인 쓰레드:29

스레드를 사용한 코드

콘솔 응용 프로그램을 작성합니다.

앞의 코드와 다른 점은 DoIt 함수의 시그니쳐를 ThreadProc과 같게 만드는 것입니다.

반환 형식이 DWORD이고 입력 인자가 LPVOID입니다.

먼저 전달받은 인자를 int 형식으로 강제 형변환합니다.

그리고 id와 현재 스레드 ID를 얻어옵니다.

반복문에서 수행하는 작업은 같습니다.

main 진입점에서는 DoIt을 스레드 진입점으로 하는 스레드를 생성합니다.

두 개의 스레드를 생성하는데 첫 번째에는 1을 인자를 전달하고 두 번째에는 2를 인자로 전달합니다.

main 함수의 반복문에서 수행하는 작업은 이전 코드와 같습니다.

#include <Windows.h>
#include <stdio.h>
DWORD DoIt(LPVOID param)
{
	int id = (int)param;
	printf("%d 스레드 ID:%d\n", id, GetCurrentThreadId());
	for (int i = 0; i < 20; i++)
	{
		printf("DoIt %d 스레드:%d\n", id,i);
		Sleep(rand() % 100);
	}
	return 0;
}
int main()
{
	printf("메인 스레드 ID:%d\n",GetCurrentThreadId());
	
	CreateThread(0, 0, DoIt, (LPVOID)1, 0, 0);
	CreateThread(0, 0, DoIt, (LPVOID)2, 0, 0);
	
	for (int i = 0; i < 30; i++)
	{
		printf("메인 스레드:%d\n", i);
		Sleep(rand() % 100);
	}
	return 0;
}

실행 결과

실행 결과를 보면 메인 스레드와 새롭게 생성한 두 개의 스레드가 서로 독립적으로 수행하는 것을 알 수 있습니다.

메인 스레드 ID:13656
메인 스레드:0
1 스레드 ID:11188
DoIt 1 스레드:0
2 스레드 ID:11772
DoIt 2 스레드:0
DoIt 2 스레드:1
DoIt 1 스레드:1
메인 스레드:1
메인 스레드:2
DoIt 1 스레드:2
DoIt 2 스레드:2
DoIt 2 스레드:3
메인 스레드:3
DoIt 1 스레드:3
DoIt 2 스레드:4
메인 스레드:4
DoIt 1 스레드:4
DoIt 1 스레드:5
메인 스레드:5
DoIt 2 스레드:5
DoIt 2 스레드:6
DoIt 1 스레드:6
메인 스레드:6
메인 스레드:7
DoIt 1 스레드:7
DoIt 2 스레드:7
DoIt 2 스레드:8
DoIt 1 스레드:8
메인 스레드:8
DoIt 1 스레드:9
메인 스레드:9
DoIt 2 스레드:9
DoIt 2 스레드:10
메인 스레드:10
DoIt 1 스레드:10
DoIt 1 스레드:11
메인 스레드:11
DoIt 2 스레드:11
DoIt 2 스레드:12
DoIt 1 스레드:12
메인 스레드:12
메인 스레드:13
DoIt 1 스레드:13
DoIt 2 스레드:13
DoIt 2 스레드:14
DoIt 1 스레드:14
메인 스레드:14
DoIt 1 스레드:15
메인 스레드:15
DoIt 2 스레드:15
메인 스레드:16
DoIt 2 스레드:16
DoIt 1 스레드:16
DoIt 2 스레드:17
메인 스레드:17
DoIt 1 스레드:17
DoIt 1 스레드:18
메인 스레드:18
DoIt 2 스레드:18
메인 스레드:19
DoIt 2 스레드:19
DoIt 1 스레드:19
메인 스레드:20
메인 스레드:21
메인 스레드:22
메인 스레드:23
메인 스레드:24
메인 스레드:25
메인 스레드:26
메인 스레드:27
메인 스레드:28
메인 스레드:29