본문 바로가기
programing/Thread

쓰레드

by RedWiz 2015. 4. 30.

 

* Kernel Object

- 커널 오브젝트

> 윈도우 운영체제가 생성해서 관리하는 리소스

프로세스, 쓰레드, 파일, 세마포어, 뮤텍스 등등

> 리소스 종류에 따라 관리의 방식도 다름

입출력할 데이터 위치, 파일의 오픈 모드(read, write), 쓰레드의 ID, 쓰레드가 속한 프로세스

> 커널 오브젝트 : 운영체제에 의해서 생성되는 리소스들은 관리 목적으로 정보를 기록하기 위해 내부적으로 생성하는 데이터 블록

> 커널 오브젝트의 생성, 관리, 소멸 시점을 결정하는 것은 운영체제

> 커널 오브젝트의 생성의 주체도 소유의 주체도 운영체제

 

- 상태

> signaled 상태 : 종료된 상태

> non-signaled 상태 : 종료되지 않은 상태

 

Thread

- main()의 호출 주체는 쓰레드, 과거에는 프로세스

 

- 단일 쓰레드 모델의 프로그램 : 쓰레드를 별도로 생성하지 않는 프로그램

 

- 멀티 쓰레드 모델의 프로그램(쓰레드를 별도로 생성)

 

- 스택을 제외한 코드 영역, 데이터 영역, 힙 영역을 공유한다

 

- 쓰레드의 ID는 프로세스의 영역을 넘어서 중복되지 않음

 

- 쓰레드 생성시 usage count : 2 (부모에서 1 + 생성된 쓰레드에서 1)

 

- 유저 레벨 쓰레드 : 라이브러리 형태로 제공되는 기능을 통해서 생성된 쓰레드

장 : 모드의 전환이 필요 없다 -> 성능이 좋다

단 : 커널은 쓰레드를 모르기 때문에 커널에 의해 블로킹시 전체가 블로킹 될 가능성 존재

-> 프로그래밍이 어렵다

 

- 커널 레벨 쓰레드 : 운영체제 기능을 통해서 생성된 쓰레드

장 : 안정성, 다양한 기능 제공

단 : 모드의 전환이 빈번히 일어남 -> 성능 저하

 

- 유저 영역 : 사용자에 의해서 (응용 프로그램 실행시) 할당된 메모리 공간 (코드, 데이터, 스택, 힙)

- 커널 영역 : 운영체제를 실행하기 위해 필요한 메모리 공간

- 유저 모드 : 유저 영역만 접근 가능

- 커널 모드 : 유저 영역 및 커널 영역 접근 가능

 

cf. 모드 전환시 시스템에 부담

 

- State

S -> Ready -> Running -> Blocked -> E

 

 Ready -> Running                 : 생성하자마자

 Ready <- Running                 : 할당된 Time Slice를 모두 소비 되어서 다른 쓰레드를 실행하기 위해

               Running -> Blocked : 입출력 연산, Sleap()함수 호출시

 Ready <------------ Blocked : Block 원인 해결시

 

* Windows API를 이용한 쓰레드 생성

 

#include <windows.h>

HANDLE CreateThread (

LPSECURITY_ATTRIBUTES       lpThreadAttributes,    // 보안설정 , default : NULL

SIZE_T                                   dwStackSize,           // 스택 크기, default : 0

LPTHREAD_START_ROUTINE    lpStartAddress,         // 쓰레드로 사용할 함수 포인터

LPVOID                                  lpParameter,             // 함수 호출시 전달할 인자

DWORD                                  dwCreationFlags,      // 바로 실행 or 대기, default : 0 (바로실행)

LPDWORD                              lpThreadID                // 생성된 쓰레드 ID (저장하기 위한 변수 포인터)

);

 

- return  : 생성된 커널 오브젝트 핸들(성공) / NULL(실패)

 

- 보통은 lpStartAddress, lpParameter 만 신경쓰고 나머지는 default(NULL or 0)

 

- lpParameter : CREATE_SUSPEND (Blocked)

STACK_SIZE_PARAM_IS_A_RESERVATION (dwStackSize는 reserve 메모리 크기 의미

                                                                                         아니면  commit 메모리 크기)

 

- CloseHandle() 를 이용해서 리소스 해제를 해야 함

 

* C라이브러리를 이용한 쓰레드 함수 (CRT런타임 라이브러리 함수)

 

#include <process.h>

unsigned long _beginthreadex(

void* security,

unsigned stack_size,

unsigned int (__stdcall* start_address)(void*),

void* arglist,

unsigned initflag,

unsigned* thrdaddr

);

 

- 인자 역할은 CreateThread()와 같음, 인자의 자료형만 조금 다름 ( 내부에서 CreateThread() 호출 )

 

- 쓰레드에 돌릴 함수는 __stdcall(아니면 WINAPI)  이어야 함 (caller가 스택 정리를 안하므로 callee가 해야함)

 

- 파라미터가 여러개면 함수 전용 구조체 만들어서 전달

> 하나까지는 문제 없지만 하나를 초과하면 (unsigned int (__stdcall*)(void*))형으로 함수를 형변환 시켜야 한다.

 

- 독립적인 메모리 블럭(_tiddata)을 CRT런타입 힙에 할당

할당한 메모리 블럭 주소를 각 쓰레드 TLS에 저장하여 연계 -> 멀티 쓰레드 문제를 해결하는 데 사용

 

- CloseHandle() 를 이용해서 리소스 해제를 해야 함

 

- "unresolved external symbol" 에러 : 라이브러리 잘못 설정

 

- _beginthread는 사용하지 않는 것이 좋음

보안 특성 설정 불가

, Suspend 상태로 생성 불가

, Thread ID 획득 불가 (쓰레드 오브젝트와 통신 불가)

, _endthread()는 종료코드로 0만 반환 (종료시 CloseHandle() 호출)

, _endthread()가 자체적으로 스레드 핸들을 닫기 때문에 스레드 커널 오브젝트에 접근 불가능한 상황 발생

 

 

cf. 멀티 쓰레드 기반의 프로그램 작성을 위한 환경설정

(error LNK2001:unresolved external symboll __beginthreadex

 error LNK2001:unresolved external symboll __endthreadex        오류 시)

 

프로젝트 환경설정 -> 구성속성 -> C/C++ -> 코드 생성 -> 런타임 라이브러리 -> '다중 스레드 디버그 DLL'

로 설정

 

cf. CreateThread() 대신 _beginthreadex를 써야하는 경우

1. 부동 소수형 변수나 함수를 사용

2. malloc, free, new, delete를 사용할 경우

3. stdio.h나 io.h에서의 함수를 사용할 경우

4. strtok()나 rand()와 같은 정적 버퍼를 사용하는 런타임 함수를 호출할 경우

 

 

* 쓰레드 종료

 

- case1 : 쓰레드 종료 시 return을 이용 (거의 대부분 정상 종료)

1) 쓰레드 함수 내에서 생성한 모든 C/C++ 오브젝트를 적절히 소멸

2) 쓰레드 스택으로 사용한 메모리 반환

3) 쓰레드의 종료 코드를 쓰레드 함수의 반환으로 설정

4) 쓰레드 커널 오브젝트의 usage count 감소

5) _beginthreadex() 함수를 통해 생성된 쓰레드의 경우 return으로 _endthread()가 자동 호출

 

cf. return 시 내부적으로 _endthreadex() 호출

(_beginthreadex() -> _threadstartex() -> _callthreadstartex() -> _endthreadex() -> ExitThread() )

 

- case 2 : ExitThread(), _endthreadex() (특정 위치에서 쓰레드 종료)

void ExitThread( DWORD dwExitCod );

void _endthreadex( unsigned retval );

1) 쓰레드 실행 도중 할당한 동적 객체가 있으면 해제가 되지 않음 (메모리 누수  가능)

2) 그러므로 미리 모두 해제 해야함

 

- case 3 : TerminateThread (외부에서 쓰레드 종료)

1) 실행 도중 갑자기 종료 (쓰레드는 자신이 종료됐다는 사실을 모름)

2) 쓰레드 스택을 정리하지 않는다 (프로세스가 죽어야 정리)

3) 안쓰는 것이 좋음


cf. 루프도는 쓰레드 종료 방법

-1-

atomic 변수 하나 잡아서 종료하면 변수를 바꿔서 루프 나오도록 함


cf. 쓰레드 종료 확인


-1-

WaitForSingleObject(), WaitForMultipleObject()에서 통과 하는지 확인


-2-

GetExitCodeThread() 로 종료코드를 받아서 STILL_ACTIVE가 아닌지 확인



cf. AfxBeginThread()

MFC 라이브러리 함수

내부적으로 _beginthreadex()으로 구현 돼 있음



* Suspend & Resume

 

- SuspendThread() : 쓰레드를 Blocked 상태로 만듦 (Suspend Count 늘림)

DWORD SuspendThread ( HANDLE hThread );

return : 성공시 suspend count

 

- ResumeThread() : 쓰레드를 Ready 상태로 만듦 (Suspend Count 줄임)

DWORD ResumeThread ( HANDLE hThread );

return : 성공시 suspend count

 

- Suspend Count : SuspendThread() 호출 빈도수 ( Running 상태일 경우 0 )

 

- Blocked 상태에서 Suspend Count 가 0이 되에어 Ready 상태가 됨

 

* Thread Priority Control

 

Priority                                    Meaning

-----------------------------------------------------

THREAD_PRIORITY_LOWEST                            -2

THREAD_PRIORITY_BELOW_NORMAL                -1

THREAD_PRIORITY_NORMAL                            0

THREAD_PRIORITY_ABOVE_NORMAL                +1

THREAD_PRIORITY_HIGHEST                            +2

 

BOOL SetThreadPriority( HANDLE hThread, int nPriority );

if error, reutn zero

 

int GetThreadPriority( HANDLE hThread );

if error, return THREAD_PRIORITY_ERROR_RETURN

 

'programing > Thread' 카테고리의 다른 글

쓰레드 풀  (0) 2017.02.23
Fiber  (0) 2015.06.09
[링크] 멀티쓰레드 프로그래밍이 왜이리 힘드나요?  (0) 2015.06.02
타이버 기반 동기화  (0) 2015.05.01
쓰레드 동기화  (0) 2015.04.30