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[] = { 1347810 };
    DWORD total = 0;
    DWORD result = 0;
 
    hThread[0=
        CreateThread(
            NULL0,
            ThreadProc,
            (LPVOID)(&paramThread[0]),
            0&dwThreadID[0]
        );
 
    hThread[1=
        CreateThread(
            NULL0,
            ThreadProc,
            (LPVOID)(&paramThread[2]),
            0&dwThreadID[1]
        );
 
    hThread[2=
        CreateThread(
            NULL0,
            ThreadProc,
            (LPVOID)(&paramThread[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);

이 함수에 대해서는 설명하지 않아도 알 것이다.



쓰레드 우선순위


나중에 다시바야하려나?


+ Recent posts