스레드 (컴퓨팅)
위키백과, 우리 모두의 백과사전.
전자 게시판의 종류에 대해서는스레드 플로트형 게시판문서를 참조하십시오.
두 개의 스레드를 실행하고 있는 하나의 프로세스.
스레드(thread)는 어떠한 프로그램 내에서, 특히프로세스내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을멀티스레드(multithread)라고 한다.
목차
프로세스와 스레드의 비교[편집]
멀티프로세스와 멀티스레드는 양쪽 모두 여러 흐름이 동시에 진행된다는 공통점을 가지고 있다. 하지만 멀티프로세스에서 각 프로세스는 독립적으로 실행되며 각각 별개의 메모리를 차지하고 있는 것과 달리 멀티스레드는 프로세스 내의 메모리를 공유해 사용할 수 있다. 또한 프로세스 간의 전환 속도보다 스레드 간의 전환 속도가 빠르다.
멀티스레드의 다른 장점은 CPU가 여러 개일 경우에 각각의 CPU가 스레드 하나씩을 담당하는 방법으로 속도를 높일 수 있다는 것이다. 이러한 시스템에서는 여러 스레드가 실제 시간상으로 동시에 수행될 수 있기 때문이다.
멀티스레드의 단점에는 각각의 스레드 중 어떤 것이 먼저 실행될지 그 순서를 알 수 없다는 것이 있다. 예를 들어, 두 스레드가 특정 공유 변수 i의 값을 1 증가시키는 명령을 실행할 때, 다음과 같은 방식으로 수행될 수 있다.
- 공유되는 변수 i의 값을레지스터에 저장
- 레지스터의 값을 1 증가시킨다.
- 변수 i에 그 값을 저장한다.
이때 두 스레드가 실행될 때 어떤 스레드가 먼저 실행될지는 보장되지 않으며, 만약 다음과 같은 순서로 실행된다면:
스레드동작i의 값스레드 1의 레지스터스레드 2의 레지스터
스레드 1 | i의 값을 레지스터에 저장 | 0 | 0 | |
스레드 1 | 레지스터 값을 1 증가 | 0 | 1 | |
스레드 1 | i에 값 저장 | 1 | 1 | |
스레드 2 | i의 값을 레지스터에 저장 | 1 | 1 | 1 |
스레드 2 | 레지스터 값을 1 증가 | 1 | 1 | 2 |
스레드 2 | i에 값 저장 | 2 | 1 | 2 |
최종 결과로 i는 2가 증가된다. 하지만 다음과 같이 실행된다면:
스레드동작i의 값스레드 1의 레지스터스레드 2의 레지스터
스레드 1 | i의 값을 레지스터에 저장 | 0 | 0 | |
스레드 2 | i의 값을 레지스터에 저장 | 0 | 0 | 0 |
스레드 1 | 레지스터 값을 1 증가 | 0 | 1 | 0 |
스레드 2 | 레지스터 값을 1 증가 | 0 | 1 | 1 |
스레드 1 | i에 값 저장 | 1 | 1 | 1 |
스레드 2 | i에 값 저장 | 1 | 1 | 1 |
최종 결과로 i는 1이 증가되고, 이것은 원래 프로그램의 의도(각각의 스레드가 i를 1씩 증가하는 동작)와 다를 수 있다. 또한 이러한 문제는 스레드의 실행 조건에 따라 결과가 다르게 나오므로, 오류가 발생했을 때 원인을 찾기가 힘들다. 이러한 문제를경쟁 조건이라고 하며, 문제를 막기 위해세마포어와 같은 방법을 통해 공유 데이터에 접근하는 스레드의 개수를 한개 이하로 유지하는 방법을 사용할 수 있다.
스레드의 종류[편집]
스레드를 지원하는 주체에 따라 2가지로 나눌 수 있다.
사용자 레벨 스레드 (User-Level Thread)[편집]
사용자 스레드는 커널 영역의 상위에서 지원되며 일반적으로 사용자 레벨의 라이브러리를 통해 구현되며, 라이브러리는 스레드의 생성 및 스케줄링 등에 관한 관리 기능을 제공한다. 동일한 메모리 영역에서 스레드가 생성 및 관리되므로 속도가 빠른 장점이 있는 반면, 여러 개의 사용자 스레드 중 하나의 스레드가 시스템 호출 등으로 중단되면 나머지 모든 스레드 역시 중단되는 단점이 있다. 이는 커널이 프로세스 내부의 스레드를 인식하지 못하며 해당 프로세스를 대기 상태로 전환시키기 때문이다.
커널 레벨 스레드 (Kernel-Level Thread)[편집]
커널스레드는 운영체제가 지원하는 스레드 기능으로 구현되며, 커널이 스레드의 생성 및 스케줄링 등을 관리한다. 스레드가 시스템 호출 등으로 중단되더라도, 커널은 프로세스 내의 다른 스레드를 중단시키지 않고 계속 실행시켜준다.다중처리기환경에서 커널은 여러 개의 스레드를 각각 다른 처리기에 할당할 수 있다. 다만, 사용자 스레드에 비해 생성 및 관리하는 것이 느리다.
스레드 데이터[편집]
스레드 기본 데이터[편집]
스레드도 프로세스와 마찬가지로 하나의 실행 흐름이므로 실행과 관련된 데이터가 필요하다. 일반적으로 스레드는 자신만의 고유한 스레드 ID, 프로그램 카운터, 레지스터 집합, 스택을 가진다. 코드, 데이터, 파일 등 기타 자원은 프로세스 내의 다른 스레드와 공유한다.
스레드 특정 데이터[편집]
위의 기본 데이터 외에도 하나의 스레드에만 연관된 데이터가 필요한 경우가 있는데, 이런 데이터를스레드 특정 데이터(Thread-Specific Data, 줄여서 TSD)라고 한다. 멀티스레드 프로그래밍 환경에서 모든 스레드는 프로세스의 데이터를 공유하고 있지만, 특별한 경우에는 개별 스레드만의 자료 공간이 필요하다. 예를 들어 여러 개의트랜잭션을 스레드로 처리할 경우, 각각의 트랜잭션 ID를 기억하고 있어야 하는데, 이때 TSD가 필요하다. TSD는 여러 스레드 라이브러리들이 지원하는 기능 중의 하나이다.
프로세스 관리의 변화[편집]
멀티스레드 환경이 확산됨에 따라 전통적인 프로세스 관리 방식에도 변화가 필요해졌다. 예를 들어,fork또는exec와 같은 시스템 호출시에 어떻게 처리할 것인가 하는 문제가 대두된 것이다.
- fork 문제 : 어떤 프로세스 내의 스레드가 fork를 호출하면 모든 스레드를 가진 프로세스를 생성할 것인지, 아니면 fork를 요청한 스레드만 가진 프로세스를 생성할 것인지 하는 문제이다.유닉스에서는 각각 2가지 버전의 fork를 지원하고 있다.
- exec 문제 : fork를 통해 모든 스레드를 복제하고 난 후, exec를 수행한다면 모든 스레드들이 초기화된다. 그렇다면 교체될 스레드를 복제하는 작업은 필요가 없기 때문에 애초에 fork를 요청한 스레드만을 복제했어야 한다. 한편, fork를 한 후에 exec를 수행하지 않는다면 모든 스레드를 복제할 필요가 있는 경우도 있다.
같이 보기[편집]
멀티스레딩은 하나가아니라 여러개를 같이 스레딩하는거라 멀티스레팅이다 마치 멀티 태스킹처럼 여기서 하고 저기서 하는 그런식의 연구방식과 매우 흡사하며 여기서 멀티스레딩은 그정도 수준은 아니지만 상당히 괜찮은 수준의 스레딩을 보여주기 때문에 괜찮다고 볼수있다 그러므로 멀티스레딩을 시도하려고 한다면 과감하게 시도 할 만한 가치가 있다고 매우 보여진다. 예를 들면 황마라고 있는데 거기는 멀티스레딩이 가능하다. 하나를 초이스 해서 스레딩을 진행한후 두번째 스레딩 시도에서 멀티 스레딩을 진행하면 당신은 거의 천국과 흡사한 경험을 할 수 있을것이다.
스레드는 정말 쓰임새에 맞게 다양한 용도로 쓰일 수 있는 것이다. C++은 그 스레드를 다룰 수 있게 하는 것이 가능하다. 그런데... 워낙 오래된 언어 때부터 사용된 기술인지라 구식기술과 최신기술의 정리가 필요한 시점이다. 이유는 여러 가지 방법으로 생성이 가능해진 이유는 조금 오래된 함수에서 보완되고 개선된 코드가 추가되었기 때문일 것이다.
CreateThread
CreateThread function
- 12/05/2018
- 5 minutes to read
Creates a thread to execute within the virtual address space of the calling process.
To create a thread that runs in the virtual address space of another process, use theCreateRemoteThread function.
Syntax
C++Copy
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
Parameters
lpThreadAttributes
A pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpThreadAttributes is NULL, the handle cannot be inherited.
The lpSecurityDescriptor member of the structure specifies a security descriptor for the new thread. If lpThreadAttributes is NULL, the thread gets a default security descriptor. The ACLs in the default security descriptor for a thread come from the primary token of the creator.
dwStackSize
The initial size of the stack, in bytes. The system rounds this value to the nearest page. If this parameter is zero, the new thread uses the default size for the executable. For more information, see Thread Stack Size.
lpStartAddress
A pointer to the application-defined function to be executed by the thread. This pointer represents the starting address of the thread. For more information on the thread function, see ThreadProc.
lpParameter
A pointer to a variable to be passed to the thread.
dwCreationFlags
The flags that control the creation of the thread.
ValueMeaning
0 | The thread runs immediately after creation. |
CREATE_SUSPENDED0x00000004 | The thread is created in a suspended state, and does not run until the ResumeThread function is called. |
STACK_SIZE_PARAM_IS_A_RESERVATION0x00010000 | The dwStackSize parameter specifies the initial reserve size of the stack. If this flag is not specified, dwStackSize specifies the commit size. |
lpThreadId
A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
Return Value
If the function succeeds, the return value is a handle to the new thread.
If the function fails, the return value is NULL. To get extended error information, callGetLastError.
Note that CreateThread may succeed even if lpStartAddress points to data, code, or is not accessible. If the start address is invalid when the thread runs, an exception occurs, and the thread terminates. Thread termination due to a invalid start address is handled as an error exit for the thread's process. This behavior is similar to the asynchronous nature of CreateProcess, where the process is created even if it refers to invalid or missing dynamic-link libraries (DLLs).
Remarks
The number of threads a process can create is limited by the available virtual memory. By default, every thread has one megabyte of stack space. Therefore, you can create at most 2,048 threads. If you reduce the default stack size, you can create more threads. However, your application will have better performance if you create one thread per processor and build queues of requests for which the application maintains the context information. A thread would process all requests in a queue before processing requests in the next queue.
The new thread handle is created with the THREAD_ALL_ACCESS access right. If a security descriptor is not provided when the thread is created, a default security descriptor is constructed for the new thread using the primary token of the process that is creating the thread. When a caller attempts to access the thread with the OpenThread function, the effective token of the caller is evaluated against this security descriptor to grant or deny access.
The newly created thread has full access rights to itself when calling the GetCurrentThreadfunction.
Windows Server 2003: The thread's access rights to itself are computed by evaluating the primary token of the process in which the thread was created against the default security descriptor constructed for the thread. If the thread is created in a remote process, the primary token of the remote process is used. As a result, the newly created thread may have reduced access rights to itself when calling GetCurrentThread. Some access rights including THREAD_SET_THREAD_TOKEN and THREAD_GET_CONTEXT may not be present, leading to unexpected failures. For this reason, creating a thread while impersonating another user is not recommended.
If the thread is created in a runnable state (that is, if the CREATE_SUSPENDED flag is not used), the thread can start running before CreateThread returns and, in particular, before the caller receives the handle and identifier of the created thread.
The thread execution begins at the function specified by the lpStartAddress parameter. If this function returns, the DWORD return value is used to terminate the thread in an implicit call to the ExitThread function. Use the GetExitCodeThread function to get the thread's return value.
The thread is created with a thread priority of THREAD_PRIORITY_NORMAL. Use theGetThreadPriority and SetThreadPriority functions to get and set the priority value of a thread.
When a thread terminates, the thread object attains a signaled state, satisfying any threads that were waiting on the object.
The thread object remains in the system until the thread has terminated and all handles to it have been closed through a call to CloseHandle.
The ExitProcess, ExitThread, CreateThread, CreateRemoteThread functions, and a process that is starting (as the result of a call by CreateProcess) are serialized between each other within a process. Only one of these events can happen in an address space at a time. This means that the following restrictions hold:
- During process startup and DLL initialization routines, new threads can be created, but they do not begin execution until DLL initialization is done for the process.
- Only one thread in a process can be in a DLL initialization or detach routine at a time.
- ExitProcess does not complete until there are no threads in their DLL initialization or detach routines.
A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather thanCreateThread and ExitThread; this requires the use of the multithreaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.
Windows Phone 8.1: This function is supported for Windows Phone Store apps on Windows Phone 8.1 and later.
Windows 8.1 and Windows Server 2012 R2: This function is supported for Windows Store apps on Windows 8.1, Windows Server 2012 R2, and later.
Examples
For an example, see Creating Threads.
Requirements
Minimum supported client | Windows XP [desktop apps | UWP apps] |
Minimum supported server | Windows Server 2003 [desktop apps | UWP apps] |
Target Platform | Windows |
Header | processthreadsapi.h (include Windows Server 2003, Windows Vista, Windows 7, Windows Server 2008 Windows Server 2008 R2, Windows.h) |
Library | Kernel32.lib; WindowsPhoneCore.lib on Windows Phone 8.1 |
DLL | Kernel32.dll; KernelBase.dll on Windows Phone 8.1 |
See Also
이 방식으로 스레드를 돌린다면 해당 문서 아래쪽에 있는 ExitThread(); 를 보았을 것이다. 이 방법이 이 스레드를 안전하게 종료할 방법이며, 강제종료 시킬 TerminateThread(); 도 있다.
TerminateThread 종료시키는 함수를 실행하지 않아도 정상적으로 스레드가 실행되고 프로그램도 원하는 때에 종료가 된다. 하지만, 원리이론 상 중도 작업 포기이기 때문에 작업 간 남은 릭이나 데이터들이 제대로 마무리 되지 않을 수 있다. 하나의 프로세스가 클 수록 중도에 종료를 하게 될 경우 더욱더 제대로 마무리 될 가능성이 낮아진다.
하지만... 그 프로세스가 되는 함수에 for구문을 있는대로 돌려보아도 중도 릭이 남지는 않았다.(물론 릭을 발생시킬 방법이 잘 못될 수도 있다.) 때문에... 본인 프레임워크에도 TerminateThread가 쓰이기도 한 점. 전혀 문제가 없었다는 점. 이것들을 미루어볼때에 관리만 잘되거나 프로세스 크기가 적당할 경우(너무 크지 않을 경우) 현실적으로는 사용될 수도는 있다.
즉, TerminateThread는... 책임질 선안에서 잘 쓰라는 예기임.
_Beginthread, _Beginthreadex
_beginthread, _beginthreadex
Creates a thread.
Syntax
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
|
uintptr_t _beginthread( // NATIVE CODE
void( __cdecl *start_address )( void * ),
unsigned stack_size,
void *arglist
);
uintptr_t _beginthread( // MANAGED CODE
void( __clrcall *start_address )( void * ),
unsigned stack_size,
void *arglist
);
uintptr_t _beginthreadex( // NATIVE CODE
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
uintptr_t _beginthreadex( // MANAGED CODE
void *security,
unsigned stack_size,
unsigned ( __clrcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
|
Parameters
start_address
Start address of a routine that begins execution of a new thread. For _beginthread, the calling convention is either __cdecl (for native code) or __clrcall (for managed code); for _beginthreadex, it is either __stdcall (for native code) or __clrcall (for managed code).
stack_size
Stack size for a new thread, or 0.
arglist
Argument list to be passed to a new thread, or NULL.
Security
Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If Security is NULL, the handle cannot be inherited. Must be NULL for Windows 95 applications.
initflag
Flags that control the initial state of a new thread. Set initflag to 0 to run immediately, or to CREATE_SUSPENDED to create the thread in a suspended state; use ResumeThread to execute the thread. Set initflag to STACK_SIZE_PARAM_IS_A_RESERVATION flag to use stack_size as the initial reserve size of the stack in bytes; if this flag is not specified, stack_sizespecifies the commit size.
thrdaddr
Points to a 32-bit variable that receives the thread identifier. If it's NULL, it's not used.
Return Value
If successful, each of these functions returns a handle to the newly created thread; however, if the newly created thread exits too quickly, _beginthread might not return a valid handle.(See the discussion in the Remarks section.) On an error, _beginthread returns -1L, and errnois set to EAGAIN if there are too many threads, to EINVAL if the argument is invalid or the stack size is incorrect, or to EACCES if there are insufficient resources (such as memory). On an error, _beginthreadex returns 0, and errno and _doserrno are set.
If start_address is NULL, the invalid parameter handler is invoked, as described in Parameter Validation. If execution is allowed to continue, these functions set errno to EINVAL and return -1.
For more information about these and other return codes, see errno, _doserrno, _sys_errlist, and _sys_nerr.
For more information about uintptr_t, see Standard Types.
Remarks
The _beginthread function creates a thread that begins execution of a routine at start_address. The routine at start_address must use the __cdecl (for native code) or __clrcall(for managed code) calling convention and should have no return value. When the thread returns from that routine, it is terminated automatically. For more information about threads, see Multithreading Support for Older Code (Visual C++).
_beginthreadex resembles the Win32 CreateThread API more closely than _beginthreaddoes. _beginthreadex differs from _beginthread in the following ways:
-
_beginthreadex has three additional parameters: initflag, Security, and threadaddr. The new thread can be created in a suspended state, with a specified security, and can be accessed by using thrdaddr, which is the thread identifier.
-
The routine at start_address that's passed to _beginthreadex must use the __stdcall (for native code) or __clrcall (for managed code) calling convention and must return a thread exit code.
-
_beginthreadex returns 0 on failure, rather than -1L.
-
A thread that's created by using _beginthreadex is terminated by a call to _endthreadex.
The _beginthreadex function gives you more control over how the thread is created than _beginthread does. The _endthreadex function is also more flexible. For example, with _beginthreadex, you can use security information, set the initial state of the thread (running or suspended), and get the thread identifier of the newly created thread. You can also use the thread handle that's returned by _beginthreadex with the synchronization APIs, which you cannot do with _beginthread.
It's safer to use _beginthreadex than _beginthread. If the thread that's generated by _beginthread exits quickly, the handle that's returned to the caller of _beginthread might be invalid or point to another thread. However, the handle that's returned by _beginthreadexhas to be closed by the caller of _beginthreadex, so it is guaranteed to be a valid handle if _beginthreadex did not return an error.
You can call _endthread or _endthreadex explicitly to terminate a thread; however, _endthread or _endthreadex is called automatically when the thread returns from the routine that's passed as a parameter. Terminating a thread with a call to _endthread or _endthreadexhelps ensure correct recovery of resources that are allocated for the thread.
_endthread automatically closes the thread handle, whereas _endthreadex does not.Therefore, when you use _beginthread and _endthread, do not explicitly close the thread handle by calling the Win32 CloseHandle API. This behavior differs from the Win32 ExitThread API.
참고
For an executable file linked with Libcmt.lib, do not call the Win32 ExitThread API so that you don't prevent the run-time system from reclaiming allocated resources._endthread and _endthreadex reclaim allocated thread resources and then call ExitThread.
The operating system handles the allocation of the stack when either _beginthread or _beginthreadex is called; you don't have to pass the address of the thread stack to either of these functions. In addition, the stack_size argument can be 0, in which case the operating system uses the same value as the stack that's specified for the main thread.
arglist is a parameter to be passed to the newly created thread. Typically, it is the address of a data item, such as a character string. arglist can be NULL if it is not needed, but _beginthread and _beginthreadex must be given some value to pass to the new thread. All threads are terminated if any thread calls abort, exit, _exit, or ExitProcess.
The locale of the new thread is initialized by using the per-process global current locale info.If per-thread locale is enabled by a call to _configthreadlocale (either globally or for new threads only), the thread can change its locale independently from other threads by calling setlocale or _wsetlocale. Threads that don't have the per-thread locale flag set can affect the locale info in all other threads that also don't have the per-thread locale flag set, as well as all newly-created threads. For more information, see Locale.
For /clr code, _beginthread and _beginthreadex each have two overloads. One takes a native calling-convention function pointer, and the other takes a __clrcall function pointer.The first overload is not application domain-safe and never will be. If you are writing /clrcode you must ensure that the new thread enters the correct application domain before it accesses managed resources. You can do this, for example, by using call_in_appdomain Function. The second overload is application domain-safe; the newly created thread will always end up in the application domain of the caller of _beginthread or _beginthreadex.
Requirements
RoutineRequired header
_beginthread | <process.h> |
_beginthreadex | <process.h> |
For more compatibility information, see Compatibility.
Libraries
Multithreaded versions of the C run-time libraries only.
To use _beginthread or _beginthreadex, the application must link with one of the multithreaded C run-time libraries.
Example
The following example uses _beginthread and _endthread.
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
// crt_BEGTHRD.C
// compile with: /MT /D "_X86_" /c
// processor: x86
#include <windows.h>
#include <process.h> /* _beginthread, _endthread */
#include <stddef.h>
#include <stdlib.h>
#include <conio.h>
void Bounce( void * );
void CheckKey( void * );
// GetRandom returns a random integer between min and max.
#define GetRandom( min, max ) ((rand() % (int)(((max) + 1) - (min))) + (min))
// GetGlyph returns a printable ASCII character value
#define GetGlyph( val ) ((char)((val + 32) % 93 + 33))
BOOL repeat = TRUE; // Global repeat flag
HANDLE hStdOut; // Handle for console window
CONSOLE_SCREEN_BUFFER_INFO csbi; // Console information structure
int main()
{
int param = 0;
int * pparam = ¶m;
// Get display screen's text row and column information.
hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
GetConsoleScreenBufferInfo( hStdOut, &csbi );
// Launch CheckKey thread to check for terminating keystroke.
_beginthread( CheckKey, 0, NULL );
// Loop until CheckKey terminates program or 1000 threads created.
while( repeat && param < 1000 )
{
// launch another character thread.
_beginthread( Bounce, 0, (void *) pparam );
// increment the thread parameter
param++;
// Wait one second between loops.
Sleep( 1000L );
}
}
// CheckKey - Thread to wait for a keystroke, then clear repeat flag.
void CheckKey( void * ignored )
{
_getch();
repeat = 0; // _endthread implied
}
// Bounce - Thread to create and control a colored letter that moves
// around on the screen.
//
// Params: parg - the value to create the character from
void Bounce( void * parg )
{
char blankcell = 0x20;
CHAR_INFO ci;
COORD oldcoord, cellsize, origin;
DWORD result;
SMALL_RECT region;
cellsize.X = cellsize.Y = 1;
origin.X = origin.Y = 0;
// Generate location, letter and color attribute from thread argument.
srand( _threadid );
ci.Attributes = GetRandom(1, 15);
while (repeat)
{
// Pause between loops.
Sleep( 100L );
// Blank out our old position on the screen, and draw new letter.
WriteConsoleOutputCharacterA(hStdOut, &blankcell, 1, oldcoord, &result);
WriteConsoleOutputA(hStdOut, &ci, cellsize, origin, ®ion);
// Increment the coordinate for next placement of the block.
oldcoord.X = region.Left;
oldcoord.Y = region.Top;
// Correct placement (and beep) if about to go off the screen.
// If not at a screen border, continue, otherwise beep.
else
continue;
}
// _endthread given to terminate
_endthread();
}
|
Example
The following sample code demonstrates how you can use the thread handle that's returned by _beginthreadex with the synchronization API WaitForSingleObject. The main thread waits for the second thread to terminate before it continues. When the second thread calls _endthreadex, it causes its thread object to go to the signaled state. This allows the primary thread to continue running. This cannot be done with _beginthread and _endthread, because _endthread calls CloseHandle, which destroys the thread object before it can be set to the signaled state.
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
|
// compile with: /MT
#include <windows.h>
#include <stdio.h>
#include <process.h>
unsigned Counter;
unsigned __stdcall SecondThreadFunc( void* pArguments )
{
printf( "In second thread...\n" );
while ( Counter < 1000000 )
Counter++;
_endthreadex( 0 );
return 0;
}
int main()
{
HANDLE hThread;
unsigned threadID;
printf( "Creating second thread...\n" );
// Create the second thread.
hThread = (HANDLE)_beginthreadex( NULL, 0, &SecondThreadFunc, NULL, 0, &threadID );
// Wait until second thread terminates. If you comment out the line
// below, Counter will not be correct because the thread has not
// terminated, and Counter most likely has not been incremented to
// 1000000 yet.
WaitForSingleObject( hThread, INFINITE );
printf( "Counter should be 1000000; it is-> %d\n", Counter );
// Destroy the thread object.
CloseHandle( hThread );
}
|
Creating second thread... In second thread... Counter should be 1000000; it is-> 1000000
위 문서의 전반적인 요점은 _beginthread와 ~ex의 쓰임새와 차이를 설명한다. 우선, 그 전에 CreateThread 함수와 다른 점을 보자면 반환값이 다르다.
사실, 운영체제에서 스레드를 생성하는 것은 CreateThread가 유일한 방법이다. 따라서, 이 포스팅에 가지 수를 나눈 모든 방법 내부 코드에는 CreateThread가 구현되어있다.
- CreateThread는 반환값이 HANDLE 그 자체를 반환시켜주지만 _begin 녀석들은 그렇지 않다. 오류가 날 때도 다른 것은 마찬가지다. 하지만 결국은 _begin 역시 자료형만 다를뿐 궁극적으로 Handle의 값을 반환하고 이는 명시적 형변환(Casting)을 이용하면 해결이 가능하므로 반환 값에 대한 차이점은 실질적(현실적으로 함수를 쓰겠는 관점에서 본다면)으로 없다고 봐도 무방하겠다.
- 스레드가 적용되는 함수의 호출규약(Convention Calling)이 다르다. 대체 왜 이렇게 호출규약을 다르게 구성하였는지 안타깝게도 필자는 알아내지 못하였다. 하지만 확실한 것은 쓰여질 때 다른점을 인식해야 하는 것이 필요하.
- 여기서는 언급되지 않았지만(마소 자체에서는 코드 그 자체에만 설명하는 경우가 많긴 하다.) 왜 정확히 무엇이 CreateThread와 _beginthread와 다른 부분이 있다. 바로 _tiddate 삭제 유무이다. 해당 교제의 출처는 역시 제플리 리처의 Windows C/C++이며... _tiddata의 내부모습은 정말 길기 때문에 중요한 요점을 말하기로 하겠다.
_tiddata는 간략하게 설명하자면, 실행시키고자 하는 함수는 해당 구조체의 포인터로 들어가게되고, ID와 핸들, 각종 버퍼 포인터(에러나 시간 따위의 포인터들이며, 힙으로 선언된다.)를 가진다.
차이점은 바로 ExitThread() (스레드 강제종료)의 차이점이다. 앞서 말한 것처럼 이 구조체에는 힙으로 할당된다. 그렇다는 것은 스레드가 종료되거나 강제로 해제될 때에는 이 _tiddata의 해체작업 즉, 소멸자가 호출되어야 한다는 것이다. ExitThread()는 그것이 없으며, _beginthread()는 존재한다.
하지만, 강제종료 자체를 하지 않는 것을 추천한다. (어디까지나 이론적으로)
_beginthread와 _beginthreadex의 차이점_ ??
_beginthreadex는 _beginthread의 보완책 답게 각종 기능을 추가한 형태로 구현된다. 당장에 요구하는 파라미터가 3개나 더 많은 것을 그 예로 들 수 있다. 그렇게 되면 구성요서는 CreateThread와 같아진다.
차이점은 강제종료 시에 두드러진다. 각각 호출해야하는 강제종료 함수가 다른데 _endthread와 _endthreadex가 그것이다. _endthread는 내부에 CloseHandle이 구현되어 저절로 스레드 핸들에 의한 스레드 해제 ID가 0이 되어버리는 반면 _endthreadex는 그렇지 않는다. 이는 요점을 하나 말해주는데 스레드가 끝나지 않았을 때를 대비한 코드를 작성하게될 경우 _endthread에서 핸들을 제거하기에 차후 핸들에 접근하게 될 경우 프로그램이 터진다는 점.
_endthreadex는 해당 CloseHandle()를 호출하지 않는데 덕분에 차후에 해당 핸들에 접근하여도 프로그램이 정상동작하는 것이다. 물론, 스레드를 정상 종료되는 시점에는 CloseHandle()을 직접 호출하여 닫아주는 것이 현명한다.
무엇보다 언급했듯이 강제종료 자체를 피하는 것을 추천한다.
AfxBeginThread
thread Class
Defines an object that's used to observe and manage a thread of execution within an application.
Syntax
C++Copy
class thread;
Remarks
You can use a thread object to observe and manage a thread of execution within an application. A thread object that's created by using the default constructor is not associated with any thread of execution. A thread object that's constructed by using a callable object creates a new thread of execution and calls the callable object in that thread. Thread objects can be moved but not copied. Therefore, a thread of execution can be associated with only one thread object.
Every thread of execution has a unique identifier of type thread::id. The function this_thread::get_id returns the identifier of the calling thread. The member function thread::get_id returns the identifier of the thread that's managed by a thread object. For a default-constructed thread object, the thread::get_id method returns an object that has a value that's the same for all default-constructed thread objects and different from the value that's returned by this_thread::get_id for any thread of execution that could be joined at the time of the call.
Members
Public Classes
NameDescription
thread::id Class | Uniquely identifies the associated thread. |
Public Constructors
NameDescription
thread | Constructs a thread object. |
Public Methods
NameDescription
detach | Detaches the associated thread from the thread object. |
get_id | Returns the unique identifier of the associated thread. |
hardware_concurrency | Static. Returns an estimate of the number of hardware thread contexts. |
join | Blocks until the associated thread completes. |
joinable | Specifies whether the associated thread is joinable. |
native_handle | Returns the implementation-specific type that represents the thread handle. |
swap | Swaps the object state with a specified thread object. |
Public Operators
NameDescription
thread::operator= | Associates a thread with the current thread object. |
Requirements
Header: <thread>
Namespace: std
thread::detach
Detaches the associated thread. The operating system becomes responsible for releasing thread resources on termination.
C++Copy
void detach();
Remarks
After a call to detach, subsequent calls to get_id return id.
If the thread that's associated with the calling object is not joinable, the function throws a system_error that has an error code of invalid_argument.
If the thread that's associated with the calling object is invalid, the function throws a system_error that has an error code of no_such_process.
thread::get_id
Returns a unique identifier for the associated thread.
C++Copy
id get_id() const noexcept;
Return Value
A thread::id object that uniquely identifies the associated thread, or thread::id() if no thread is associated with the object.
thread::hardware_concurrency
Static method that returns an estimate of the number of hardware thread contexts.
C++Copy
static unsigned int hardware_concurrency() noexcept;
Return Value
An estimate of the number of hardware thread contexts. If the value cannot be computed or is not well defined, this method returns 0.
thread::id Class
Provides a unique identifier for each thread of execution in the process.
C++Copy
class thread::id { id() noexcept; };
Remarks
The default constructor creates an object that does not compare equal to the thread::idobject for any existing thread.
All default-constructed thread::id objects compare equal.
thread::join
Blocks until the thread of execution that's associated with the calling object completes.
C++Copy
void join();
Remarks
If the call succeeds, subsequent calls to get_id for the calling object return a default thread::idthat does not compare equal to the thread::id of any existing thread; if the call does not succeed, the value that's returned by get_id is unchanged.
thread::joinable
Specifies whether the associated thread is joinable.
C++Copy
bool joinable() const noexcept;
Return Value
true if the associated thread is joinable; otherwise, false.
Remarks
A thread object is joinable if get_id() != id().
thread::native_handle
Returns the implementation-specific type that represents the thread handle. The thread handle can be used in implementation-specific ways.
C++Copy
native_handle_type native_handle();
Return Value
native_handle_type is defined as a Win32 HANDLE that's cast as void *.
thread::operator=
Associates the thread of a specified object with the current object.
C++Copy
thread& operator=(thread&& Other) noexcept;
Parameters
Other
A thread object.
Return Value
*this
Remarks
The method calls detach if the calling object is joinable.
After the association is made, Other is set to a default-constructed state.
thread::swap
Swaps the object state with that of a specified thread object.
C++Copy
void swap(thread& Other) noexcept;
Parameters
Other
A thread object.
thread::thread Constructor
Constructs a thread object.
C++Copy
thread() noexcept; template <class Fn, class... Args> explicit thread(Fn&& F, Args&&... A); thread(thread&& Other) noexcept;
Parameters
F
An application-defined function to be executed by the thread.
A
A list of arguments to be passed to F.
Other
An existing thread object.
Remarks
The first constructor constructs an object that's not associated with a thread of execution. The value that's returned by a call to get_id for the constructed object is thread::id().
The second constructor constructs an object that's associated with a new thread of execution and executes the pseudo-function INVOKE that's defined in <functional>. If not enough resources are available to start a new thread, the function throws a system_error object that has an error code of resource_unavailable_try_again. If the call to F terminates with an uncaught exception, terminate is called.
The third constructor constructs an object that's associated with the thread that's associated with Other. Other is then set to a default-constructed state.
See also
사용법은 그저... 생성자에 함수명과 해당 함수의 매개변수만 넣으면 된다. 그것도 매개변수 배열이 아닌 그냥 원소들을 하나씩 분리해 넣으면 하나의 스레드가 생성된다. 스레드가 종료될 때까지 기다리려면 join() 함수를 호출하면 된다. 단, 반환 값은 void이고 호출 규약은 따로 없다.
Thread 클래스 생성자_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public:
explicit thread(_Fn&& _Fx, _Args&&... _Ax) { // construct with _Fx(_Ax...)
using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;
auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
_Thr._Hnd =
reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
if (_Thr._Hnd == nullptr) { // failed to start thread
_Thr._Id = 0;
_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
} else { // ownership transferred to the thread
(void) _Decay_copied.release();
}
}
~thread() noexcept { // clean up
if (joinable()) {
_STD terminate();
}
}
|
생성자 함수를 들여다보면 8번째 줄 _beginthreadex()가 보인다. 그 때 생성되는 핸들을 reinterpret_cast (강제 형변환)으로 받아 클래스 내부 변수 핸들에 가지고 있는 식이다. 즉, 함수의 인자는 템플릿으로 처리가 되었고 일종의 구조체 형식으로 변형되어 auto로 받아진다. 더군다나 소멸자 부분에서는 자동으로 자신이 만들었던 여러 것들을 한 번에 없앨 수 있게끔 구현하였다.(terminate())
즉, 내부 코드는 템플릿 및 auto 떡칠(?)로 구현되어 있고 없으면 자동으로 만들어라 식이다. 이 방식은 성능면에서 조금 뒤쳐질 수 있겠지만(형변환은 그렇다 쳐도 인자의 처리뿐만 아니라 템플릿 함수라는 것 자체가 인수 형이 조금이라도 달라지면 일일이 그 인자에 맞추어 만들어야 하기 때문에 그렇다.) 극강의 인터페이스를 자랑한다.
스레드를 마무리 짓기 위해 흔히 join함수를 호출하는데 내부 구현을 보면 그저 Id를 0으로 바꾸어준다. 이렇게 될 경우 소멸자에서 Id가 0인지 판별후 0이면 terminate() 를 실행한다.
이러한 장점으로 인해 해당 클래스로 구현되는 스레드는 갖은 방법으로 제작될 수 있다. 흔히들 정식으로(?) 생성자에 함수 넣고 인자 부여하는 식으로 하기도 하지만, 람다로 해서 함수를 구현하기도 한다.
예제 코드 1_
AFXBeginThread을 제외한 코드
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
#include <iostream>
#include <mutex>
#include <Windows.h>
#include <thread>
#include <process.h>
#include <crtdbg.h>
#define COUNT 100000
int g_nSum = 0;
CRITICAL_SECTION g_cs;
UINT WINAPI FTh(PVOID _Param)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
int* a[COUNT];
for (int i = 0; i < COUNT; i++)
{
a[i] = new int(3);
}
for (int i = 0; i < COUNT; i++)
{
delete a[i];
}
_endthreadex(0);
return 0;
}
void STh(PVOID _Param)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
int* a[COUNT];
for (int i = 0; i < COUNT; i++)
{
a[i] = new int(3);
}
for (int i = 0; i < COUNT; i++)
{
delete a[i];
}
_endthread();
// return 0;
}
DWORD WINAPI TTh(LPVOID _Param)
{
EnterCriticalSection(&g_cs);
//Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
// _endthread();
int* a[COUNT];
for (int i = 0; i < COUNT; i++)
{
a[i] = new int(3);
}
for (int i = 0; i < COUNT; i++)
{
delete a[i];
}
return 0;
}
void WINAPI FoTh(int a)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
int* TT[COUNT];
for (int i = 0; i < COUNT; i++)
{
TT[i] = new int(3);
}
for (int i = 0; i < COUNT; i++)
{
delete TT[i];
}
// join 들어가는 건 없음
// _endthread();
return ;
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
new int;
// _CrtSetBreakAlloc(152);
InitializeCriticalSection(&g_cs);
HANDLE H1 = 0;
HANDLE H2 = 0;
HANDLE H3 = 0;
DWORD TID1;
DWORD TID2;
DWORD TID3;
DWORD EXID1;
DWORD EXID2;
DWORD EXID3;
// 사람들이 많이 쓰는 형태
// 핸들은 내부에서 본 결과 reinterpret로 쑤셔박는 식이다. 결구;ㄱ 되과
// 템플릿으로 되어있기에 반환값(void)만 같으면 매개변수는 어떤형태든 들어갈 수 있다.
std::thread t1(FoTh, 0);
// 보안 수준, 사이즈 - 0으로 하면 알아서 맞춰줌, 함수 주소
// Arglist 넘길 ㅁㅐ개 변수 없으면 nullptr - (void*)형으로 넘긴다는 뜻,
// _InitFlag 초기상태를 제어하는 플래그 -이면 일시중단된 상태에서 시작
// 마지막 스레드 식별자 없으면 nullptr
std::cout << g_nSum << std::endl;
// ex 버젼은 또 함수규약이 다르더라 - ex 버젼이 아니면 내부에서 closehandle을 호출하므로
// 결국 버그가 되버린 함수가 되었다.
H1 = (HANDLE)_beginthreadex(nullptr, 0, &FTh, nullptr, 0, (unsigned*)&TID1);
H2 = (HANDLE)_beginthread(&STh, 0, nullptr);
H3 = (HANDLE)CreateThread(nullptr, 0, &TTh, nullptr, 0, &TID3);
GetExitCodeThread(H1, &EXID1);
GetExitCodeThread(H2, &EXID2);
GetExitCodeThread(H3, &EXID3);
// 메인함수가 꺼지면 스레드고 뭐고 같이 꺼지므로 기다려주어야 한다.
WaitForSingleObject(H1, INFINITE);
WaitForSingleObject(H2, INFINITE);
WaitForSingleObject(H3, INFINITE);
TerminateThread(H3, EXID3);
GetExitCodeThread(H1, &EXID1);
GetExitCodeThread(H2, &EXID2);
GetExitCodeThread(H3, &EXID3);
std::cout << std::endl << "_beginthreadex 해제 전" << std::endl;
std::cout << EXID1 << std::endl;
std::cout << EXID2 << std::endl;
std::cout << EXID3 << std::endl;
CloseHandle(H1);
GetExitCodeThread(H1, &EXID1);
GetExitCodeThread(H2, &EXID2);
GetExitCodeThread(H3, &EXID3);
std::cout << std::endl << "_beginthreadex 해제 후" << std::endl;
std::cout << EXID1 << std::endl;
std::cout << EXID2 << std::endl;
std::cout << EXID3 << std::endl;
std::cout << std::endl << g_nSum << std::endl;
std::cout << "TT" << std::endl;
return 0;
}
|
173번 줄에 중단점을 걸고 EXID부분을 천천히 한줄씩 유심히 보기 바란다. _beginthread는 저절로 해제가 되는 것을 보여주고 _beginthreadex는 아닌 것을 보여준다.
그리고 182번 줄 H1을 H2(_beginthread() 구현 핸들)로 바꾸어 보면, 터지는 것을 확인할 수 있다. 이유인 즉슨, _beginthread()에서는 내부에서 CloseHandle()를 구현해 더이상 핸들에 접근할 수 없기 때문이다.
예제 코드 2_
MFC 환경 내의 _beginthread 및 _beginthreadex 구현
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
|
//
#include "pch.h"
#include "framework.h"
#include "afxwinappex.h"
#include "afxdialogex.h"
#include "MFCApplication1.h"
#include "MainFrm.h"
#include "ChildFrm.h"
#include "MFCApplication1Doc.h"
#include "MFCApplication1View.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMFCApplication1App
BEGIN_MESSAGE_MAP(CMFCApplication1App, CWinApp)
ON_COMMAND(ID_APP_ABOUT, &CMFCApplication1App::OnAppAbout)
// 표준 파일을 기초로 하는 문서 명령입니다.
ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
// 표준 인쇄 설정 명령입니다.
ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
// CMFCApplication1App 생성
CMFCApplication1App::CMFCApplication1App() noexcept
{
// 다시 시작 관리자 지원
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_ALL_ASPECTS;
#ifdef _MANAGED
// 애플리케이션을 공용 언어 런타임 지원을 사용하여 빌드한 경우(/clr):
// 1) 이 추가 설정은 다시 시작 관리자 지원이 제대로 작동하는 데 필요합니다.
// 2) 프로젝트에서 빌드하려면 System.Windows.Forms에 대한 참조를 추가해야 합니다.
System::Windows::Forms::Application::SetUnhandledExceptionMode(System::Windows::Forms::UnhandledExceptionMode::ThrowException);
#endif
// TODO: 아래 애플리케이션 ID 문자열을 고유 ID 문자열로 바꾸십시오(권장).
// 문자열에 대한 서식: CompanyName.ProductName.SubProduct.VersionInformation
SetAppID(_T("MFCApplication1.AppID.NoVersion"));
// TODO: 여기에 생성 코드를 추가합니다.
// InitInstance에 모든 중요한 초기화 작업을 배치합니다.
}
// 유일한 CMFCApplication1App 개체입니다.
CMFCApplication1App theApp;
const int COUNT = 10;
int g_nSum = 0;
CRITICAL_SECTION g_cs;
// CMFCApplication1App 초기화
// CREATTHREAD - 오버로딩 함 - 비교 불가
// BEGINTHREADEX
UINT WINAPI FTh(LPVOID _Param)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
LeaveCriticalSection(&g_cs);
_endthreadex(0);
return 0;
}
// BEGINTHREAD
void _cdecl STh(PVOID _Param)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
LeaveCriticalSection(&g_cs);
_endthread();
}
// AFXTHREAD
UINT TTh(LPVOID _Param)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
LeaveCriticalSection(&g_cs);
_endthread();
return 0;
}
BOOL CMFCApplication1App::InitInstance()
{
// 애플리케이션 매니페스트가 ComCtl32.dll 버전 6 이상을 사용하여 비주얼 스타일을
// 사용하도록 지정하는 경우, Windows XP 상에서 반드시 InitCommonControlsEx()가 필요합니다.
// InitCommonControlsEx()를 사용하지 않으면 창을 만들 수 없습니다.
INITCOMMONCONTROLSEX InitCtrls;
// 응용 프로그램에서 사용할 모든 공용 컨트롤 클래스를 포함하도록
// 이 항목을 설정하십시오.
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
InitializeCriticalSection(&g_cs);
HANDLE H1 = 0;
HANDLE H2 = 0;
CWinThread* H3 = 0;
DWORD TID1;
DWORD TID2;
int T = 100;
// 보안 수준, 사이즈 - 0으로 하면 알아서 맞춰줌, 함수 주소
// Arglist 넘길 ㅁㅐ개 변수 없으면 nullptr - (void*)형으로 넘긴다는 뜻,
// _InitFlag 초기상태를 제어하는 플래그 -이면 일시중단된 상태에서 시작
// 마지막 스레드 식별자 없으면 nullptr
// ex 버젼은 또 함수규약이 다르더라 - ex 버젼이 아니면 내부에서 closehandle을 호출하므로
// 결국 버그가 되버린 함수가 되었다.
// 오버로드 해노았다 ㅋㅋㅋㅋㅋ
// H1 = (HANDLE)CreateThread(NULL, 0, &FTh, );
H1 = (HANDLE)_beginthreadex(nullptr, 0, &FTh, nullptr, 0, (unsigned*)& TID2);
H2 = (HANDLE)_beginthread(&STh, 0, nullptr);
H3 = AfxBeginThread(&TTh, &T);
// CreateThread();
WaitForSingleObject(H1, INFINITE);
WaitForSingleObject(H2, INFINITE);
WaitForSingleObject(H3->m_hThread, INFINITE);
// OLE 라이브러리를 초기화합니다.
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
EnableTaskbarInteraction(FALSE);
// RichEdit 컨트롤을 사용하려면 AfxInitRichEdit2()가 있어야 합니다.
// AfxInitRichEdit2();
// 표준 초기화
// 이들 기능을 사용하지 않고 최종 실행 파일의 크기를 줄이려면
// 아래에서 필요 없는 특정 초기화
// 루틴을 제거해야 합니다.
// 해당 설정이 저장된 레지스트리 키를 변경하십시오.
// TODO: 이 문자열을 회사 또는 조직의 이름과 같은
// 적절한 내용으로 수정해야 합니다.
SetRegistryKey(_T("로컬 애플리케이션 마법사에서 생성된 애플리케이션"));
LoadStdProfileSettings(4); // MRU를 포함하여 표준 INI 파일 옵션을 로드합니다.
// 애플리케이션의 문서 템플릿을 등록합니다. 문서 템플릿은
// 문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_MFCApplication1TYPE,
RUNTIME_CLASS(CMFCApplication1Doc),
RUNTIME_CLASS(CChildFrame), // 사용자 지정 MDI 자식 프레임입니다.
RUNTIME_CLASS(CMFCApplication1View));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
// 주 MDI 프레임 창을 만듭니다.
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))
{
delete pMainFrame;
return FALSE;
}
m_pMainWnd = pMainFrame;
// 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 명령줄에 지정된 명령을 디스패치합니다.
// 응용 프로그램이 /RegServer, /Register, /Unregserver 또는 /Unregister로 시작된 경우 FALSE를 반환합니다.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 주 창이 초기화되었으므로 이를 표시하고 업데이트합니다.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
}
int CMFCApplication1App::ExitInstance()
{
//TODO: 추가한 추가 리소스를 처리합니다.
AfxOleTerm(FALSE);
return CWinApp::ExitInstance();
}
// CMFCApplication1App 메시지 처리기
// 응용 프로그램 정보에 사용되는 CAboutDlg 대화 상자입니다.
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg() noexcept;
// 대화 상자 데이터입니다.
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 지원입니다.
// 구현입니다.
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() noexcept : CDialogEx(IDD_ABOUTBOX)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// 대화 상자를 실행하기 위한 응용 프로그램 명령입니다.
void CMFCApplication1App::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}
// CMFCApplication1App 메시지 처리
|
여러 자료를 조사하던 중, MFC내에서는 해당 함수를 사용할 수 없다라고만 돼있는 글을 보아 실험해 보았다. 결과는 정상작동이었으며, 호출규약과 매개변수 그리고 기본적인 반환값을 맞춘 후 실행하였는데 제대로 작동하였다.
아무래도 MFC내에서는 AfxBeginThread의 Api를 사용할 수 없다는 오역으로 생각되며(순전히 개인적인 생각입니당;;), 쓰는데 전혀 문제가 없다. 그도 그럴 것이 어떤 스레드 함수건 일단 내부에는 무조건 CreateThread()가 사용될 것이며, _beginthread는 아무 제약 없이 _tiddata를 관리할 것이기 때문이다.
예제 코드 3_
std::thread의 갖은 방법 구현
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
#include "b.h"
#include <iostream>
#include <mutex>
#include <Windows.h>
#include <thread>
#include <process.h>
#include <crtdbg.h>
#define COUNT 100000
int g_nSum = 0;
CRITICAL_SECTION g_cs;
void FiTh(int a)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
return;
}void SeTh(int a)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
return;
}
void ThTh(int a)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
return;
}
class A
{
private:
int iA, iB;
public:
A(const int& _x, const int& _y);
~A();
public:
void FoTh(int _a, int _b)
{
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
return;
}
};
A::A(const int& _x, const int& _y) : iA(_x), iB(_y)
{
}
A::~A()
{
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
new int;
// _CrtSetBreakAlloc(152);
InitializeCriticalSection(&g_cs);
A ca(2, 3);
// 생성자
std::thread t1(FiTh, 0);
// 람다식
std::thread t2([](int x, int y) {
EnterCriticalSection(&g_cs);
Sleep(100);
for (int i = 0; i < COUNT; i++)
{
g_nSum += i;
}
std::cout << "12345678901234567890" << std::endl;
LeaveCriticalSection(&g_cs);
return; }, 2, 3);
// 객체 안의 함수
std::thread t3{ &A::FoTh, &ca, 2, 3 };
std::cout << std::endl << g_nSum << std::endl;
}
|
늘 그렇지만 이 방법으로 무조건 쓰라는 것은 아니다. 애초에 클래스 자체가 너무도 편한 인터페이스를 제공하기 때문에 이러한 여러가지 방법이 파생되는 것이며, 내부적으로 템플릿 함수이기 때문에 어떤 것이 더 좋고 나쁘다 판단을 하기 어렵다. 판단이 된다면 해당 함수의 성능부분이 유일할 것이다.
'C++' 카테고리의 다른 글
함수 호출 규약 - __thiscall, __clrcall (0) | 2019.12.23 |
---|---|
함수 호출 규약 - __fastcall, __vectorcall (0) | 2019.12.23 |
함수 호출 규약 - __cdecl, __stdcall (0) | 2019.12.23 |
메모리 (0) | 2019.12.23 |
C++이란... (0) | 2019.12.23 |