C-Language/Thread

C Windows Thread examples

Soul-Learner 2017. 1. 9. 22:27

C 프로그래밍, 윈도우에서 Thread 사용하기


C언어에서 쓰레드를 생성하는 방법에는 2가지가 있는데  CreateThread()함수를 사용하는 것과 _beginthread(), _beginthreadex()함수를 사용하는 것이다

_beginthread, _beginthreadex를 사용해야 안전한 경우

  • 부동 소수형 변수나 함수를 사용할 경우
  • C의 malloc과 free나 C++ 의 new와 delete 를 사용할경우 
  • stdio.h 나 io.h에서 어떤 함수를 호출한다면
  • strtok() 나 rand() 와 같이 정적 버퍼를 사용 하는 어떤 런타임 함수를 호출할 경우

_beginthread(), _beginthreadex() 함수도 내부에서는 CreateThread()함수를 사용한다

위의 경우를 제외하고는  CreateThread()함수를 사용하는 것이 안전하다고 한다


쓰레드를 종료하는 방법에는 여러가지 함수를 사용하는 방법이 있으나 가장 권장되는 방법은 쓰레드가 루프를 벗어나서 자연스럽게 멈추도록 유도하는 것이다. 아래와 같은 함수를 사용할 수도 있지만 주의사항을 잘 알고 사용해야 한다

  •  ExitThread(), TerminateThread()
  • _endthread(), _endthreadex()


main 쓰레드와 워커 쓰레드(Worker Thread)

main 함수가 실행되는 것은 쓰레드 측면에서 생각할 때 main 쓰레드가 실행되는 것이다. main 쓰레드는 실행되는 중에 다른 쓰레드를 생성할 수 있는데, main 쓰레드가 종료하면 main 쓰레드가 생성한 모든 쓰레드도 따라서 종료하게 된다. 그러므로 main 쓰레드가 종료되지 않고 다른 쓰레드를 기다리게 하려면 다음과 같은 함수를 사용한다

WaitForSingleObject() : main 쓰레드에게 지정된 한개의 쓰레드를 기다리게 한다

WaitForMultipleObjects() : main 쓰레드에게 지정된 다수개의 쓰레드를 기다리게 한다


Windows API 함수( CreateThread )를 이용한 쓰레드 생성)

아래의 코드에서는 2개의 쓰레드를 생성하고 쓰레드 모두에게 동일한 함수를 실행하도록 함으로써 함수내의 지역변수의 값이 2개의 쓰레드에 의해 공유되는지 안되는지를 확인하고 일정 조건이 되면 쓰레드가 루프를 벗어나 종료하도록 하였다

#include <windows.h>
#include <stdio.h>

// 쓰레드에 의해서 실행될 함수. 이 함수가 종료하면 쓰레드도 종료된다
DWORD WINAPI ThreadFunc1(void* data) {

	int cnt = 0;
	while (1) {
		int id = GetCurrentThreadId();
		printf("id=%d, ThreadFunc1:%d \n", id, cnt++);
		Sleep(2000);
	}
	return 0;
}

DWORD WINAPI ThreadFunc2(void* data) {

	int cnt = 0;
	while (1) {
		int id = GetCurrentThreadId();
		printf("id=%d, ThreadFunc2:%d \n", id, cnt++);
		Sleep(2000);
	}
	return 0;
}

int main() {
	HANDLE thread1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
	if (thread1) {
		// Optionally do stuff, such as wait on the thread.
		printf("Waiting1...\n");
	}

	HANDLE thread2 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
	if (thread2) {
		// Optionally do stuff, such as wait on the thread.
		printf("Waiting2...\n");
	}

	WaitForSingleObject(thread1, INFINITE);
	WaitForSingleObject(thread2, INFINITE);

	// main 쓰레드가 종료되는 것을 막기위해 아래처럼 할 수도 있다
	/*
	while (1) {
		Sleep(10000);
	} */
}


Windows에서 제공하는 C/C++ 라이브러리를 이용한 쓰레드 생성하기

2개의 쓰레드가 실행되면서 동일함수를 실행한다. 쓰레드는 코드를 공유할 수 있지만 쓰레드 함수 내에 선언된 지역변수는 공유되지 않고 각각의 쓰레드가 관리하는 스택에 생성되어 각각의 쓰레드가 자기만의 지역변수를 가지게 된다.

아래의 코드는 2개의 쓰레드가 동일한 함수를 실행(코드를 공유)하면서 함수 안에 선언된 지역변수의 값을 올려가다가 일정 값에 도달하면 쓰레드가 루프를 벗어나 종료하는 내용이다. 지역변수가 2개의 쓰레드에서 공유되지 않고 각각의 값을 가지고 사용되는 것을 확인할 수 있다.

Visual Studio에서 _beginthreadex(), _endthreadex()를 사용하기 위해서는 <process.h> 가 필요함

uintptr_t _beginthreadex(

          void *_Security,

          unsigned int _StackSize,

          unsigned int(*_StartAdrdress)(void *),

          void *_ArgList,

          unsigned int _InitFlag,

          unsigned int *_ThrdAddr    );


#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <process.h>

unsigned int WINAPI ThreadFunction(void *arg);
void Error(const char *mes);

int main()
{
	// 첫번째 쓰레드 설정
	HANDLE hThread = NULL;
	DWORD dwThreadID = NULL;
	hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, NULL, 0, (unsigned*)&dwThreadID);
	if (hThread == 0) Error("_beginthreadex Error\n");
	printf("생성된 쓰레드의 핸들 : %d\n", (int)hThread);
	printf("생성된 쓰레드의 ID : %d\n", dwThreadID);

	// 두번째 쓰레드 설정
	HANDLE hThread2 = NULL;
	DWORD dwThreadID2 = NULL;
	hThread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, NULL, 0, (unsigned*)&dwThreadID2);
	if (hThread2 == 0) Error("_beginthreadex Error\n");
	printf("생성된 쓰레드의 핸들 : %d\n", (int)hThread2);
	printf("생성된 쓰레드의 ID : %d\n", dwThreadID2);

	// main 쓰레드가 종료하면 워커쓰레드도 종료하므로 무한 대기하게 한다
	WaitForSingleObject(hThread, INFINITE);

	printf("main 함수 종료!!\n");
	return 0;
}

unsigned int WINAPI ThreadFunction(void *arg)
{
	int cnt = 0;
	while (1)
	{
		Sleep(2000);
		int id = GetCurrentThreadId();
		printf("id=%d, 쓰레드 실행 중(%d)\n", id, cnt++);

		if (cnt == 5) break; // 특정 조건이 되면 쓰레드가 멈추도록 유도한다
	}
	printf("id=%d 쓰레드 종료 \n", GetCurrentThreadId());
	return 0;
}

void Error(const char *mes)
{
	printf("%s\n", mes);
	exit(0);
}


만약 쓰레드 함수로 아규먼트를 전달할 때는 다음과 같은 방법을 사용하면 된다

int num = 10;

_beginthreadex(NULL, 0, ThreadFunction, &num, 0, (unsigned*)&dwThreadID);


위에서 전달한 아규먼트를 쓰레드 함수에서 아규먼트를 받을 때는 다음과 같이 한다

unsigned int WINAPI ThreadFunction(void *arg){

int* pNum = (int*) arg;

printf("전달된 숫자=%d \n", *pNum);

......

}