행복한 연어의 이야기

(윈도우 시스템) 12. 쓰레드의 생성과 소멸 본문

IT/윈도우 시스템 프로그래밍

(윈도우 시스템) 12. 쓰레드의 생성과 소멸

해피살몬 2021. 8. 13. 22:32

1. Windows 에서의 쓰레드 생성과 소멸

쓰레드의 생성

HANDLE CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAttributes,	//핸들 상속 여부
	SIZE_T dwStackSize,				//쓰레드에게 할당되는 스택 크기
	LPTHREAD_START_ROUTINE lpStartAddress,		//쓰레드의 main 역할을 하는 함수
	LPVOID lpParameter,				//쓰레드 함수에 전달할 인자
	DWORD dwCreationFlags,				//쓰레드의 생성 및 실행을 조절
	LPDWORD lpThreadId				//쓰레드의 ID를 전달 받기 위한 주소값
);

WIndows 에서 메모리가 허용하는 만큼 쓰레드를 생성 가능하다.

멀티 쓰레드 기반 프로그래밍에서 쓰레드의 흐름을 예측하는 것은 불가능하다.

시스템의 당시 상황에 따라서 다르게 동작하기 때문에 예측은 의미가 없는 일이다.

쓰레드의 소멸

1. return 문

쓰레드 함수내에서 return 문을 통해 소멸시키는 방법이 가장 이상적이다.

main 쓰레드의 경우 return 은 조금 다른 의미를 가지는데 

main 쓰레드는 프로세스를 대표하는 쓰레드이기 때문에 main 쓰레드의 return 의 경우 프로세스 종료를 의미하게 된다.

이 경우 생성한 쓰레드가 돌고있다가 main 쓰레드가 return 할 경우 생성된 쓰레드는 작업을 마치기 전에 종료한다.

2. ExitThread 함수

VOID ExitThread(
	DWORD dwExitCode		//커널 오브젝트에 등록되는 쓰레드 종료코드
);

BOOL GetExitCodeThread(
	HANDLE hThread,			//종료코드를 얻기위한 쓰레드의 핸들
	LPDWROD lpExitCode		//얻게되는 종료코드를 저장할 메모리의 주소값
);

아래 함수로 종료코드를 얻어와 위 함수를 호출하는 방법이다.

쓰레드 함수가 A 함수에서 B 함수, B 함수에서 C 함수를 호출한다고 가정했을 때

return 문을 사용한다면 C, B, A, 쓰레드 함수의 return을 거쳐 종료하게 되고

ExitThread 함수를 사용한다면 C 에서 바로 쓰레드가 종료하게 된다.

 

2. 쓰레드의 성격과 특성

동시접근에 있어서의 문제점

앞서 11. 쓰레드의 이해 에서 쓰레드는 같은 프로세스간 Code, Data, Heap 영역을 공유한다고 설명한 바 있다.

이는 문제가 발생 할 수 있다.

쓰레드 A 와 B 가 전역변수 num을 ++ 하는 함수를 각각 5번씩 호출한다고 가정 했을 때

 기대값은 10 이지만 그렇지 않을 수 있다.

예를들어 A쓰레드에서 레지스터가 더한 값을 메모리에 저장하기 전 (레지스터 1, 메모리 0) 

컨텍스트 스위칭이 일어난다면 B 쓰레드는 메모리에 있는 값 0 을 가져올 것이다.

다시 컨텍스트 스위칭이 일어나 A 쓰레드에서 메모리에 1을 저장하고 

컨텍스트 스위칭이 다시 일어나 B 쓰레드도 1의 값을 메모리에 저장한다고 했을때

함수가 현재 2번 호출되어 2의 값이 저장되어야 하지만 1이 저장되어 있다.

이를 해결하기위한 방법으로는 C 라이브러리에서 제공하는 _beginthreadex 함수를 사용하는 방법이 있다.

_beginthreadex 함수는 독립적인 메모리 블록을 할당한다.

이경우 ExitThread 함수를 호출하는 대신 _endthreadex 함수를 호출해 주어야 한다.

 

프로세스로부터의 쓰레드 분리

  같은 프로세스내에서 생성된 쓰레드는 프로세스의 핸들 테이블 까지 공유한다.

자식 프로세스의 경우 Usage Count 가 2가 된다고 했는데 (부모가 잠조, 자신이 참조)

쓰레드 또한 마찬가지이다.

쓰레드 생성후 바로 쓰레드의 핸들을 반환하는 것이 프로세스로부터 쓰레드 분리를 뜻한다.

생성시 2 핸들 반환시 1 쓰레드 종료시 0 이되어 쓰레드 종료와 동시에 쓰레드를 반환하기 때문이다.

핸들 반환을 하지 않는다면 종료 후에도 Usage Count 가 1이 남아 프로세스 종료시까지 

커널 오브젝트 남아있게 된다.

 

3. 쓰레드 컨트롤

쓰레드의 상태 컨트롤 (Ready, Running, Blocked)

DWORD SuspendThread(
	HANDLE hThread		//Blocked 상태로 만들 쓰레드의 핸들
);

DWORD ResumeThread(
	HANDLE hThread		//Ready 상태로 만들 쓰레드의 핸들
);

 

위 함수는 쓰레드를 Blocked 상태로 만드는 함수, 아래 함수는 쓰레드를 Ready 상태로 만드는 함수이다.

 SuspendThread(Blocked 상태로 만드는 함수) 를 호출하면 서스펜드 카운트가 1 올라가게 되고

ResumeThread(Ready 상태로 만드는 함수) 를 호출하면 서스펜드 카운트가 1 줄어든다.

스케줄러는 서스펜드 카운터가 0 이 되어야 쓰레드를 Ready 상태에 놓는다.

(2번 SuspendThread 함수를 호출하면 ResumeThread 함수도 2번 호출 필요)

 

쓰레드의 우선순위 컨트롤 

BOOL SetThreadPriority(
	HANDLE hThread,
	int nPriority
);

int GetThreadPriority(
	HANDLE hThread
);

위 함수는 쓰레드의 우선순위를 변경하는 함수이고, 아래 함수는 쓰레드의 우선순위를 가져오는 함수이다.

쓰레드의 우선순위를 프로세스 우선순위와 쓰레드의 상대적 우선순위의 조합으로 결정된다.

프로세스간 우선순위를 먼저 판단하고 프로세스 내 쓰레드에서 우선순위를 판단하는 방식이다.

 

 

알고 넘어가야할 것

1. CreateThread 함수와 _beginthreadex 함수 차이점

2. 둘 이상의 쓰레드가 동시접근하는 메모리 공간의 문제점

3. 쓰레드의 상태변화 

4. 프로세스로부터의 쓰레드 분리

 

'윤성우 저자'님의 '뇌를 자극하는 윈도우즈 시스템 프로그래밍' 책을 보고 정리한 내용입니다.

Comments