HANDLE
CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
첫 번째 인자는 프로세스 생성 할 때도 본 것이다. 핸들의 상속 여부를 결정한다.
두 번째 인자는 쓰레드의 스택 크기를 지정하기 위한 매개변수이다.
0을 전달하면 디폴트 사이즈인 1M가 적용된다.
세 번째 인자는 쓰레드의 main 역할을 하는 함수를 지정하는 인자이다.
인자타입 LPTHREAD_START_ROUNTE 인데, 반환타입이 DWORD 이고 매개변수 타입은 LPVOID(void *) 인 함수 포인터로 형변환 되어있다.
네 번째 인자는 쓰레드 함수에 전달할 인자를 지정하는 용도이다.
lpStartAddress 가 가리키는 함수 호출 시 전달할 인자를 지정하는 것이다.
Main 의 argv 생각하면 된다.
다섯 번 째 인자는 쓰레드의 생성 및 실행을 조절하기 위한 전달인자이다
CREATE_SUSPENDED 가 전달되면, 생성과 동시에 Blocked 상태가 된다.
그러나 아래에서 나올 함수 ResumeThread 가 호출되면 실행한다.
XP 이상에서는 인자로 STACK_SIZE_PARAM_IS_A_RESERVATION 을 전달 할 수 있는데, 이 경우 dwStackSize 를 통해 전달되는 값의 크기는 reserve 메모리 크기를 의미하게 되고, 그렇지 않을 경우 commit 메모리 크기를 의미한다(이 내용은 나중에 설명한다. 그냥 넘어가자)
여섯 번 째 인자는 쓰레드 ID 를 전달받기 위한 변수의 주소값을 전달한다.
굳이 필요없다면 NULL 을 전달하면 되는데, ME 이하에서는 NULL을 전달할 수 없다.
이 함수가 실행되면, 쓰레드의 핸들이 반환된다.
ID는 PC에 고유하다.
운영체제마다 다른데 윈도우는 제한이 없어서 유저 레벨에 허용된 메모리만큼 생성 가능
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | /* CountThread.cpp */ #include <stdio.h> #include <windows.h> #include <tchar.h> #define MAX_THREADS (1024*10) DWORD WINAPI ThreadProc(LPVOID lpParam) { DWORD threadNum = (DWORD)lpParam; while (1) { _tprintf(_T("thread num: %d \n"), threadNum); Sleep(5000); } return 0; } DWORD cntOfThread = 0; int _tmain(int argc, TCHAR* argv[]) { DWORD dwThreadId[MAX_THREADS]; HANDLE hThread[MAX_THREADS]; while (1) { hThread[cntOfThread] = CreateThread( NULL, 0, ThreadProc, (LPVOID)cntOfThread, 0, &dwThreadId[cntOfThread] ); if (hThread[cntOfThread] == NULL) { _tprintf(_T("MAXIMUM THREAD SIZE: %d \n"), cntOfThread); break; } cntOfThread++; } for (DWORD i = 0; i < cntOfThread; i++) { CloseHandle(hThread[i]); } return 0; } | cs |
쓰레드의 소멸
리턴코드로 깔끔하게 쓰레드를 끝내는 경우
(가장 권장한다고 한다)
GetExitCodeThread
GetExitCodeThread 의 인자는 쓰레드의 핸들, 종료코드를 저장할 메모리 주솟값 이다.
break마냥 쓰레드 내에서 종료하는거
그러나 잘 디자인된 프로그램의 경우를 보면 쓰레드 함수의 역할이 명확하다.
소프트웨어 디자인적으로 메인 스레드가 기타 쓰레드들의 라이프사이클을 책임지는 것이 좋다는 것.
메인 쓰레드가 B 쓰레드를 생성하는 것까진 괜찮으나, B 쓰레드가 C쓰레드를 또 생성해버린다면 별로 좋지 않다는 것이다.
디자인의 범위를 벗어나고 동작이 명확하지 않은 프로그램이 된다고 함. 결론은 쓰지말자
만약 C 함수에서 쓰레드를 종료한다고 하면, return 으로 종료하려면 다시 B , A 로 가야 하지만, ExitThread 함수를 호출하면 바로 종료가 된다.
하지만, C++ 로 할 경우, A,B 함수의 스택 프레임에 C++ 객체가 존재한다고 가정 할 경우, 그 객체의 소멸자는 호출되지 않는다. 따라서 메모리 누수가 날 수도 있다.
이로써, C,C++ 구분 없이 return 문이 가장 좋은 것 같다.
쓰지말자
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | /* ThreadAdderOne.cpp */ #include <stdio.h> #include <windows.h> #include <tchar.h> DWORD WINAPI ThreadProc(LPVOID lpParam) { DWORD * nPtr = (DWORD *)lpParam; DWORD numOne = *nPtr; DWORD numTwo = *(nPtr + 1); DWORD total = 0; for (DWORD i = numOne; i <= numTwo; i++) { total += i; } return total; } int _tmain(int argc, TCHAR* argv[]) { DWORD dwThreadID[3]; HANDLE hThread[3]; DWORD paramThread[] = { 1, 3, 4, 7, 8, 10 }; DWORD total = 0; DWORD result = 0; hThread[0] = CreateThread( NULL, 0, ThreadProc, (LPVOID)(¶mThread[0]), 0, &dwThreadID[0] ); hThread[1] = CreateThread( NULL, 0, ThreadProc, (LPVOID)(¶mThread[2]), 0, &dwThreadID[1] ); hThread[2] = CreateThread( NULL, 0, ThreadProc, (LPVOID)(¶mThread[4]), 0, &dwThreadID[2] ); if (hThread[0] == NULL || hThread[1] == NULL || hThread[2] == NULL) { _tprintf(_T("Thread creation fault! \n")); return -1; } WaitForMultipleObjects(3, hThread, TRUE, INFINITE); GetExitCodeThread(hThread[0], &result); total += result; GetExitCodeThread(hThread[1], &result); total += result; GetExitCodeThread(hThread[2], &result); total += result; _tprintf(_T("total (1 ~ 10): %d \n"), total); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(hThread[2]); return 0; } | cs |
68 : 메인쓰레드의 종료 -> 프로세스의 종료
나머지 쓰레드들이 종료될때까지 메인쓰레드의 진행을 멈추겠다는 의미
쓰레드의 성격과 특성
동시접근의 문제점
total이 10일때, A쓰레드에서 연산을 진행해 ALU에서 6을 더하고 그 값을 레지스터 r0에 저장한 채로 컨텍스트 스위칭이 일어나서,
B쓰레드에서 total의 값은 바뀌지 않았는데 또 그 total값에 9를 더해 다시 total에 저장한 채로 다시 A쓰레드로 컨텍스트 스위칭이 일어나면
레지스터 r0에 있던 값 16이 B쓰레드에서 연산한 total 19를 덮어씌워 total의 값은 16이 된다.
쓰레드의 UC는 프로세스의 그것과 같은 원리다
쓰레드가 죽을 때 쓰레드의 커널 오브젝트는 계속 누적되어서 메모리를 차지할 가능성이 있다.
그래서 쓰레드를 생성할 때 그 부모에서 CloseHandle 함수를 호출 해 쓰레드의 UC를 하나만 남겨둬야
쓰레드가 종료될 때 쓰레드 커널 오브젝트도 같이 사라진다. 이것을 쓰레드 분리라고 한다.
다만 이렇게 되면 부모가 종료 코드를 얻기 힘들어지기 때문에 그러한 경우엔 주의깊게 사용해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 | Strtok 함수를 호출되면서 처음에 등록된 문자열은 어딘가에 저장되어야만 한다. 그래서 두 번째 부터는 NULL 을 인자로 줘서 출력할 것이다. 우리는 전역, 혹은 static 으로 선언된 배열에 문자열이 저장되어 있음을 예측할 수 있다. 이 경우, 메모리의 동시 참조의 문제가 발생할 수 있다. 해결책은 MS 에서 멀티 쓰레드에 안전한 ANSI 표준 라이브러리를 제공하고 있다. 따라서, 프로젝트 설정 – C/C++ / Code Generation 에서 런타임 라이브러리를 Multi-hreaded Debug DLL 로 바꿔주면 된다. 이제 한 가지 일을 더 해줘야 한다.쓰레드를 생성 할 때,CreateThread 함수 대신 _beginthreadex 함수를 사용하면 된다.. 이 함수는 내부적으로 CreateThread 를 호출하지만, 그 전에 독립적 메모리 블록을 할당한다. Multi- 로 시작하는 이름의 표준 C 라이브러리 함수는 이렇게 할당된 쓰레드 각각의 메모리 블록을 기반으로 연산한다. 이로써 멀티 쓰레드 기반에서 안정성이 확보되는 것이다. _beginthreadex 함수는 전달인자의 순서와 의미가 CreateThread 함수와 동일하다. 다만 선언된 매개변수 자료형과 반환형에 차이가 있기 때문에, 약간 형 변환이 요구된다. | cs |
실행 결과는 차이가 없다.
앞으로는 멀티 쓰레드 기반 프로그래밍을 한다는 가정하에 _beginthreadex 함수를 사용한다.
한가지 주의할 점은 쓰레드를 종료하는 방법이다.
만약 ExitThread 함수를 활용하고자 하면, _endthreadex 를 사용하기 바란다.
인자는 동일하다.
이 함수는 _beginthreadex 함수에서 메모리를 할당했으므로, 종료할 때는 그 메모리를 반환해야 한다. 그래서 사용하는 함수이다.
그럼 return 문을 이용하면 메모리 반환이 안될까
된다! 쓰레드 함수에서 return 문을 이용할 경우, _endthreadex 함수가 자동 호출된다.
그냥 return 에 의한 종료가 만사Ok 이다!.
참고,
_beginthread , _endthread 라는 이름의 함수가 있는데, 이 것들은 쓰레드 생성을 간결하게 하려고 만든 함수인데, 기능도 제한적이고 핸들도 사용 할 수 없다.
이러한 문제점들 때문에 많은 전문가들이 가급적이면 사용하지 말라고 하는 함수이다.
쓰레드의 상태 컨트롤
쓰레드의 상태는 프로그램이 실행되는 과정에서 수도없이 변경된다.
이 것은 상황에 따라 운영체제의 관리방법에 따른 것이므로, 프로그래머가 건드리는 것은 아니다.
그러나, 경우에 따라서 프로그래머가 직접 변경해 주어야 하는 상황도 있다.
특정 쓰레드를 지목하면서, 그 쓰레드의 실행을 잠시 멈추기 위해 Blocked 상태로 만들거나, 다시 실행시키기 위해 Ready 상태로 두기 위해서 필요하다.
쓰레드 함수를 Blocked 상태로 두거나 Ready 상태에 둘 때 사용하는 함수
별로 권장하지 않는다고 한다ㅇㄹㅇㄴㅁ
Windows 에서는 프로세스가 우선순위를 갖는 것이 아니라, 프로세스 안의 쓰레드가 우선순위를 가진다.
9장에서 말한 프로세스의 우선순위를 가리켜 기준 우선 순위라고 한다.
(IDLE_PRIORITY_CLASS , NORMAL_PRIORITY_CLASS 등..)
쓰레드는 상대적인 우선순위를 갖는다.
#define THREAD_PRIORITY_LOWEST -2
#define THREAD_PRIORITY_BELOW_NORMAL -1
#define THREAD_PRIORITY_NORMAL 0
#define THREAD_PRIORITY_ABOVE_NORMAL 1
#define THREAD_PRIORITY_HIGHEST 2
쓰레드의 우선순위는 프로세스의 기준 우선순위와 쓰레드의 상대 우선순위를 조합해서 결정된다.
참고로 위 표의 우선순위 상수값은 Windows 버전별 차이가 있다.
따라서 우선순위가 정확히 몇 이라고 이해하기 보다는 어느것 보다는 높겠다 정도로만 이해하면 된다.
프로세스 내 모든 쓰레드의 상대 우선순위는 THREAD_PRIORITY_NORMAL 이다.
이를 변경 , 참조할 때는 다음 두 함수를 쓴다.
BOOL SetThreadPriority(HANDLE hThread, int nPriority);
Int GetThreadPriority(HANDLE hThread);
이 함수에 대해서는 설명하지 않아도 알 것이다.
쓰레드 우선순위
나중에 다시바야하려나?
'운영체제 > 윈도우 시스템' 카테고리의 다른 글
메모리 계층 (0) | 2019.10.15 |
---|---|
쓰레드 동기화 기법 (0) | 2019.10.11 |
커널 레벨 쓰레드 유저 레벨 쓰레드 커널 모드 유저 모드 (0) | 2019.10.08 |
프로세스와 쓰레드, 윈도우 쓰레드 (0) | 2019.10.08 |
컴구세번째 함수호출 (0) | 2019.10.08 |