Understand multithreading and delve into the essential difference between CreateThread and _beginthreadex

This article will guide you through your first encounter with multithreading and delve deeply into the essential differences between CreateThread and _beginthreadex. After reading this, you'll be able to confidently use multithreading and accurately explain the distinctions between these two functions. The question is: Should you use CreateThread or _beginthreadex in real-world programming? Multithreading is actually quite simple. The main thread of the following program creates a child thread and waits for it to finish. The child thread outputs its thread ID and prints a classic message — "Hello World." The entire program is short and easy to understand. ```cpp // The simplest way to create a multi-threaded example #include #include // Thread function DWORD WINAPI ThreadFun(LPVOID pM) { printf("The thread ID of the child thread is: %d, child thread output Hello World", GetCurrentThreadId()); return 0; } // Main function, which is actually executed by the main thread int main() { printf("The easiest way to create a multi-threaded instance"); printf(" -- by MoreWindows (http://blog.csdn.net/MoreWindows) --"); HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL); WaitForSingleObject(handle, INFINITE); return 0; } ``` The output is as follows: ![Understanding Multithreading and the Key Differences Between CreateThread and _beginthreadex](http://i.bosscdn.com/blog/1G/11/22/O1-0.png) Now, let's take a closer look at some of the functions in the code. First, `CreateThread`: **Function:** Creates a thread **Prototype:** ```cpp HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); ``` **Description:** - The first parameter specifies the security attributes of the thread object. Passing `NULL` uses default settings. - The second parameter defines the stack size. A value of 0 means using the default size (typically 1MB). - The third parameter is the address of the function that the new thread will execute. - The fourth parameter is the argument passed to the thread function. - The fifth parameter sets flags for thread creation. A value of 0 means the thread starts immediately; `CREATE_SUSPENDED` suspends the thread until `ResumeThread()` is called. - The sixth parameter returns the thread ID, or `NULL` if not needed. **Return Value:** Returns a handle to the new thread on success, or `NULL` on failure. Next, `WaitForSingleObject`: **Function:** Waits for a kernel object to be signaled. **Prototype:** ```cpp DWORD WINAPI WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds ); ``` **Description:** - The first parameter is the kernel object to wait for. - The second parameter is the maximum time to wait, in milliseconds. Use `INFINITE` to wait indefinitely. When the thread finishes execution, its handle becomes signaled, allowing `WaitForSingleObject()` to proceed. Now, while `CreateThread` is a Windows API, C/C++ also provides `_beginthreadex()`. Many books, including *Windows Core Programming*, recommend using `_beginthreadex()` instead of `CreateThread`. Why? Let’s explore. One key difference lies in how each handles the standard C runtime library. The C runtime was designed before multithreading support was common. As a result, global variables like `errno` can cause issues when accessed from multiple threads. For example, if one thread calls `system()` and another modifies `errno`, the first thread may read incorrect data. To solve this, Windows provides a solution: each thread has its own memory area for CRT functions. This is managed by `_beginthreadex()`. Below is a simplified version of its source code with comments to help clarify the distinction between `_beginthreadex()` and `CreateThread()`: ```cpp // _beginthreadex source code (simplified) uintptr_t __cdecl _beginthreadex( void *security, unsigned stacksize, unsigned (__CLR_OR_STD_CALL *initialcode)(void *), void *argument, unsigned createflag, unsigned *thrdaddr ) { // Allocate and initialize per-thread data _ptiddata ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata)); _initptd(ptd, _getptd()->ptlocinfo); // Set up thread parameters ptd->_initaddr = (void *)initialcode; ptd->_initarg = argument; // Call CreateThread internally HANDLE handle = CreateThread(...); return (uintptr_t)handle; } ``` As seen, `_beginthreadex()` allocates a `_tiddata` block for each thread, ensuring that CRT functions like `strtok()` or `errno` are thread-safe. This makes `_beginthreadex()` the safer choice when working with the standard C library. Let’s now create multiple threads using `_beginthreadex()`: ```cpp // Creating multiple child thread instances #include #include #include unsigned int __stdcall ThreadFun(PVOID pM) { printf("The child thread whose thread ID is %4d says: Hello World", GetCurrentThreadId()); return 0; } int main() { printf("Create multiple child thread instances"); printf(" -- by MoreWindows (http://blog.csdn.net/MoreWindows) --"); const int THREAD_NUM = 5; HANDLE handle[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL); WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); return 0; } ``` Each thread prints "Hello World" with its own ID. Now, what if we want each thread to print a unique number? One approach is to use a global variable, but this leads to race conditions in a multithreaded environment. We’ll explore this further in the next section.

Laptop Holder

Shenzhen ChengRong Technology Co.,Ltd. , https://www.laptopstandsupplier.com