C++ Tutorial
Multi-Threaded Programming II Part A
Native Thread for Win32 - 2020
Microsoft Windows operating system's support for multithreaded programming is almost similar to the support provided by POSIX threads. The differences are not in the actual functionality but the names in the API functions.
Every Win32 process has at least one thread, which we call as the main thread. We will assume that the OS will give a time slice to each program thread, in round-robin fashion. In fact, the threads in a Win32 program will be competing for the CPU with threads in other programs and with system threads, and these other threads may have higher priorities.
Let's look at the simple example.The main thread spawns a new thread to increment the myCounter inside myThread function, while the main thread keeps waiting for a character input (getchar()) from the user, and prints out counter value whenever we type other than 'q' character:
To use Windows multithreading functions, we must include <windows.h> in our program. To create a thread, the Windows API supplies the CreateThread( ) function.
Each thread has its own stack (see thread vs processes). You can specify the size of the new thread's stack in bytes using the stackSize parameter which is the 2nd argument of CreateThread( ) function in the example below. If this integer value is zero, then the thread will be given a stack that is the same size as the creating thread.
#include <windows.h> #include <iostream> DWORD WINAPI myThread(LPVOID lpParameter) { unsigned int& myCounter = *((unsigned int*)lpParameter); while(myCounter < 0xFFFFFFFF) ++myCounter; return 0; } int main(int argc, char* argv[]) { using namespace std; unsigned int myCounter = 0; DWORD myThreadID; HANDLE myHandle = CreateThread(0, 0, myThread, &myCounter;, 0, &myThreadID;); char myChar = ' '; while(myChar != 'q') { cout << myCounter << endl; myChar = getchar(); } CloseHandle(myHandle); return 0; }
The output is:
0 868171493 1177338657 3782005161 4294967295 4294967295 ...
Each thread of execution begins with a call to a function, called the thread function, within the creating process. The 3rd argument of CreateThread( ) function, myThread is that thread function. Execution of the thread continues until the thread function returns. The address of this function (that is, the entry point to the thread) is specified in threadFunc.
By typing a character 'q', we can end the program.
The most basic Windows applications start with a single thread. The function call we use to create a child thread is CreateThread(). The following syntax shows the parameters passed to CreateThread().
HANDLE WINAPI CreateThread( __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in SIZE_T dwStackSize, __in LPTHREAD_START_ROUTINE lpStartAddress, __in_opt LPVOID lpParameter, __in DWORD dwCreationFlags, __out_opt LPDWORD lpThreadId );
Parameters
- lpThreadAttributes [in, optional]
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 [in]
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. - lpStartAddress [in]
A pointer to the application-defined function to be executed by the thread. This pointer represents the starting address of the thread. - lpParameter [in, optional]
A pointer to a variable to be passed to the thread. - dwCreationFlags [in]
The flags that control the creation of the thread. - lpThreadId [out, optional]
A pointer to a variable that receives the thread identifier. If this parameter is NULL, the thread identifier is not returned.
The return value from the function call is a handler for the thread, which is a different construct than the thread ID. When the call was unsuccessful, it returns 0. With the exception of the address of the function to execute, all of the parameters will take default values if they are provided the null. The following code shows how to create a child thread using the CreateThread(). The call to GetCurrentThreadId() returns an integer ID for the calling thread. It also captures the ID of the created thread. The threadID is not very useful since most functions take the thread handle as a parameter.
#include <Windows.h> #include <stdio.h> DWORD WINAPI mythread(__in LPVOID lpParameter) { printf("Thread inside %d \n", GetCurrentThreadId()); return 0; } int main(int argc, char* argv[]) { HANDLE myhandle; DWORD mythreadid; myhandle = CreateThread(0, 0, mythread, 0, 0, &mythreadid;); printf("Thread after %d \n", mythreadid); getchar(); return 0; }
Output is:
Thread after 6784 Thread inside 6784
CreateThread() tells the OS to make a new thread. But it does not set up the thread to work with the libraries provided by the developer environment.
In other words, though Windows creates the thread and returns a handle to that thread, the runtime libraries haven't set up the thread-local data structures that they need.
So, instead of calling CreateThread(), we should use the calls by the runtime libraries. The two recommended ways of creating a thread are the calls _beginthread() and _beginthreadex(). When using _beginthread() and _beginthreadex(), we must remember to link in the multithreaded library. This will vary from compiler to compiler.
These two functions take different parameters:
uintptr_t _beginthread( void( *start_address )( void * ), unsigned stack_size, void *arglist ); uintptr_t _beginthreadex( void *security, unsigned stack_size, unsigned ( *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 or __clrcall; for _beginthreadex, it is either __stdcall or __clrcall.
- 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 NULL, the handle cannot be inherited. - initflag
Initial state of a new thread (0 for running or CREATE_SUSPENDED for suspended); use ResumeThread to execute the thread. - thrdaddr
Points to a 32-bit variable that receives the thread identifier. Might be NULL, in which case it is not used.
There is another difference between these two functions other than the parameters they take. A thread created by _beginthread() will close the handle to the thread when the thread exits, while the handle returned by _beginthreadex() will have to be explicitly closed by calling CloseHandle(), which is a similar to the detached thread in POSIX.
As we see in the description of the two functions above, they are also differ by the type of function that the thread execute: _beginthread() is a void function and uses the default calling convention __cdecl, while _beginthreadex() returns an unsigned int and uses the __stdcall calling convention.
The _beginthread() and _beginthreadex() functions return handles to the newly created threads. But the actual return type of the function call is uintptr_t which has to be type cast to a HANDLE before it can be used in function calls that expect an object handle.
The following example creates threads using the three different ways:
#include <Windows.h> #include <process.h> #include <stdio.h> DWORD WINAPI mythreadA(__in LPVOID lpParameter) { printf("CreateThread %d \n", GetCurrentThreadId()); return 0; } unsigned int __stdcall mythreadB(void* data) { printf("_beginthreadex %d \n", GetCurrentThreadId()); return 0; } void mythreadC(void* data) { printf("_beginthread %d \n", GetCurrentThreadId()); } int main(int argc, char* argv[]) { HANDLE myhandleA, myhandleB, myhandleC; myhandleA = CreateThread(0, 0, mythreadA, 0, 0, 0); myhandleB = (HANDLE)_beginthreadex(0, 0, &mythreadB;, 0, 0, 0); WaitForSingleObject(myhandleB, INFINITE); myhandleC = (HANDLE)_beginthread(&mythreadC;, 0, 0); getchar(); return 0; }
Output is:
_beginthreadex 5256 CreateThread 5924 _beginthread 4292
The call to WaitForSingleObject() waits for an object to signal its readiness. In other words, the routine passes the handle to a thread and waits for that thread to terminate.
Threads created in main() will not continue to run after the main thread exits. So, the main thread must wait for the threads it created to complete before it exits the main() function. It does this by calling function WaitForMultipleObjects().
Calling _beginthread() looks more convenient because it takes fewer parameters and clean up the handle after the thread exits, however, it is better to use _beginthreadex().
Calling _beginthreadex() avoids a difficulty with _beginthread(). If the thread terminates, the handle returned by the call to _beginthread() will be invalid or even reused. Therefore, it is impossible to query the status of the thread or even be confident that the handle to the thread is a handle to the same thread to which is originally pointed. The following example demonstrates this issue:
#include <Windows.h> #include <process.h> #include <stdio.h> void mythreadA(void* data) { printf("mythreadA %d \n", GetCurrentThreadId()); } void mythreadB(void* data) { volatile int i; // Most compiler won't eliminate the loop // since i is volatile for (i = 0; i < 100000; i++) {} printf("mythreadB %d \n", GetCurrentThreadId()); } int main(int argc, char* argv[]) { HANDLE myhandleA, myhandleB; myhandleA = (HANDLE)_beginthread(&mythreadA;, 0, 0); myhandleB = (HANDLE)_beginthread(&mythreadB;, 0, 0); WaitForSingleObject(myhandleA, INFINITE); WaitForSingleObject(myhandleB, INFINITE); return 0; }
Output is:
mythreadA 5912 mythreadB 3092
The mythreadA() terminates quickly and may have already terminated by the time that the main thread reaches the call to create the second thread. If the first thread has terminated, the handle to the first thread may be reused as the handle to the second thread. Queries using the handle of the first thread might succeed, but they will work on the wrong thread. The calls to WaitForSingleObject() may not be using a correct or valid handle for either of the threads depending on the completion time of the threads. Though it is not clear from the output, still, there is a potential for things not going to work as we expect.
The following example for using _beginthreadex() is equivalent to the previous code. Thread created by _beginthreadex() need to be cleaned up by calling CloseHandle(). So, the calls to WaitForSingleObject() are certain to get the correct handles:
#include <Windows.h> #include <process.h> unsigned int __stdcall mythreadA(void* data) { return 0; } unsigned int __stdcall mythreadB(void* data) { volatile int i; // Most compiler won't eliminate the loop // since i is volatile for (i = 0; i < 100000; i++) {} return 0; } int main(int argc, char* argv[]) { HANDLE myhandleA, myhandleB; myhandleA = (HANDLE)_beginthreadex(0, 0, &mythreadA;, 0, 0, 0); myhandleB = (HANDLE)_beginthreadex(0, 0, &mythreadB;, 0, 0, 0); WaitForSingleObject(myhandleA, INFINITE); WaitForSingleObject(myhandleB, INFINITE); CloseHandle(myhandleA); CloseHandle(myhandleB); return 0; }
And the output:
mythreadA 5860 mythreadB 5312
There are several ways to make a thread to terminate. But the recommended way is for the thread to exit the function that it was instructed to run. In the following example, the thread will print out its ID and then exit:
DWORD WINAPI mythreadA(__in LPVOID lpParameter) { printf("CreateThread %d \n", GetCurrentThreadId()); return 0; }
It is also possible to make threads to terminate using the ExitThread() or TerminateThread().
- But these function calls are not recommended since they may leave the application in an unspecified state. The thread does not get the chance to release any held mutexes or free any other allocated resources.
- They also don't give the run-time libraries the opportunity to clean up any resources that they have allocated for the thread.
A thread may terminate with a call to _endthread() or _endthreadex(), as long as care is taken to ensure that resources the thread has acquired are appropriately freed. This call (_endthread() or _endthreadex()) needs to match the call that was used to create the thread. If the thread exits with a call to _endthreadex(), the handle to the thread still needs to be closed by another thread calling cloaseHandle().
For a fork-join type model, there will be a master thread that creates multiple worker threads and then waits for the worker threads to exit. There are two routines that the master thread can use to wait for the worker to complete: WaitForSingleObject() or WaitForMultipleObjects(). These two functions will wait either for the completion of a single thread or for the completion of an array of threads. The routines take the handle of the thread as a parameter together with a timeout value that indicates how long the master thread should wait for the worker thread to complete. Usually, the value INFINITE will be appropriate. The following code demonstrates the code necessary to wait for a single thread to complete:
#include <Windows.h> #include <process.h> #include <stdio.h> unsigned int __stdcall mythread(void* data) { printf("Thread %d\n", GetCurrentThreadId()); return 0; } int main(int argc, char* argv[]) { HANDLE myhandle[2]; myhandle[0] = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, 0, 0); myhandle[1] = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, 0, 0); WaitForSingleObject(myhandle[0], INFINITE); WaitForSingleObject(myhandle[1], INFINITE); CloseHandle(myhandle[0]); CloseHandle(myhandle[1]); getchar(); return 0; }
Output from the run is:
Thread 4360 Thread 1368
The following code is equivalent to the previous one. But this example is using WaitForMultipleObjects().
- The first parameter to the function call is the number of threads that are to be waited for.
- The second parameter is a pointer to the array of handles to these threads.
- The third parameter is a boolean. If true, it indicates that the function should return when all the threads are complete. If false, it indicates the function should return on the completion of the first worker thread.
- The last parameter is the length of time that the master thread should wait before returning anyway.
#include <Windows.h> #include <process.h> #include <stdio.h> unsigned int __stdcall mythread(void* data) { printf("Thread %d\n", GetCurrentThreadId()); return 0; } int main(int argc, char* argv[]) { HANDLE myhandle[2]; myhandle[0] = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, 0, 0); myhandle[1] = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, 0, 0); WaitForMultipleObjects(2, myhandle, true, INFINITE); CloseHandle(myhandle[0]); CloseHandle(myhandle[1]); getchar(); return 0; }
Output is:
Thread 4712 Thread 4244
Even after a thread created by calling _beginthreadex has exited, it will continue to hold resources. These resources need to be freed by calling the CloseHandle() function on the handle to the thread. The following example demonstrates the complete sequence of creating a thread, waiting for it to complete, and then freeing its resources:
#include <Windows.h> #include <process.h> #include <stdio.h> unsigned int __stdcall mythread(void* data) { printf("Thread %d\n", GetCurrentThreadId()); return 0; } int main(int argc, char* argv[]) { HANDLE myhandle; myhandle = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, 0, 0); WaitForSingleObject(myhandle, INFINITE); CloseHandle(myhandle); return 0; }
Output is:
Thread 4272
A suspended thread is the one that is not currently running. Threads can be created in the suspended state and then started later. If a thread is in the suspended state, then the call to start the thread executing is ResumeThread(). It takes the handle of the thread as a parameter.
There is a SuspendThread() call that will cause a running thread to be suspended. This call is expected to be used only by tools such as debugger. Suspending a running thread may lead to problems if the thread currently holds resources such as mutexes.
The following code demonstrates the creation of a suspended thread and then calling ResumeThread() on that thread. The code uses a call to getchar(), which waits for the enter key to be pressed, to separate the creation of the thread from the act of resuming thread:
#include <Windows.h> #include <process.h> #include <stdio.h> unsigned int __stdcall mythread(void* data) { printf("Thread %d\n", GetCurrentThreadId()); return 0; } int main(int argc, char* argv[]) { HANDLE myhandle; myhandle = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, CREATE_SUSPENDED, 0); getchar(); ResumeThread(myhandle); getchar(); WaitForSingleObject(myhandle, INFINITE); CloseHandle(myhandle); return 0; }
Output we get after we hit the return key:
(hit Return key) Thread 4580
The suspended state of the thread is handled as a counter, so multiple calls to SuspendThread() need to be matched with multiple calls to ResumeThread().
Each thread of execution has associated with it a suspend count. If this count is zero, then the thread is not suspended. If it is nonzero, the thread is in a suspended state. Each call to SuspendThread( ) increments the suspend count. Each call to ResumeThread( ) decrements the suspend count. A suspended thread will resume only when its suspend count has reached zero. Therefore, to resume a suspended thread implies that there must be the same number of calls to ResumeThread( ) as there have been calls to SuspendThread( ).
Many of the Windows API functions return handles. As we saw from the earlier discussion of type casting, these are really just unsigned integers. However, they have a particular purpose. Windows API calls that return handles have actually caused a resource to be created within the kernel space. The handle is just an index for that resource. When the application has finished with the resource, the call to CloseHandle() enables the kernel to free the associated kernel space resources.
Resources with handles can be shared between processes. Once a resource exists, other processes can open a handle to that resource or duplicate an existing handle to the resource. It is important to know that the handle of a kernel resource makes sense only within the context of the process that has access to the resource. Passing the value of the handle to another process does not enable the other process to get access to the resource. The kernel needs to enable access to the resource and provide a new handle for the existing resource in the new process.
Some functions do not return a handle. For these functions, there is no associated kernel resource. So, it is not necessary to call CloseHandle() once the resource is no longer needed.
Threading with QT5:
- QThreads - Introduction
- QThreads - Creating Threads
- QThreads - Priority
- QThreads - QMutex
- QThreads - GuiThread
- QThreads - wait()
- QTcpServer - Client and Server using MultiThreading
- QTcpServer - Client and Server using QThreadPool
- Asynchronous QTcpServer - Client and Server using QThreadPool
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization