Detailed analysis of the difference between CreateThread() and beginthread()

Many developers don't know the relationship between the two. They choose a function to use at random, and find that there is no major problem, so they are busy solving more urgent tasks. When one day suddenly discovers that a program has a slight memory leak when it runs for a long time, the developer will never think that it is the result of mixing these two sets of functions.

We know that there are two ways to create a thread under Windows, one is to call Windows API CreateThread() to create a thread; the other is to call the function _beginthread() or _beginthreadex() of MSVC CRT to create a thread. The corresponding exit thread also has two functions ExitThread() of Windows API and _endthread() of CRT. These two sets of functions are used to create and exit threads, what is the difference between them?
Many developers don't know the relationship between the two. They choose a function to use at random and find that there is no major problem, so they are busy solving more urgent tasks without delving into them. When one day suddenly discovers that a program has a slight memory leak when it runs for a long time, the developer will never think that it is the result of mixing these two sets of functions.
According to the relationship between Windows API and MSVC CRT, it can be seen that _beginthread() is a wrapper for CreateThread(), and it finally calls CreateThread() to create a thread. So what did _beginthread() do before calling CreateThread()? We can look at the source code of _beginthread(), which is located in thread.c in the CRT source code. ***We can find that it applied for a structure called _tiddata before calling CreateThread(), and then initialized this structure with the _initptd() function and passed it to _beginthread()'s own thread entry function _threadstart. _threadstart first saves the _tiddata structure pointer passed by _beginthread() to the explicit TLS array of the thread, and then it calls the user's thread entry to actually start the thread. ***After the user thread ends, the _threadstart() function calls _endthread() to end the thread. And _threadstart also wraps the user thread entry function with __try/__except to capture all unhandled signals and hand them over to the CRT for processing.
So in addition to the signal, it is obvious that the main purpose of the CRT wrapping the Windows API thread interface is that _tiddata. What is stored in this thread-private structure? We can find its definition from mtdll.h, which stores information related to CRT such as thread ID, thread handle, erron, strtok()'s previous call position, rand() function seed, exception handling, etc. is thread-private information. It can be seen that MSVC CRT does not use the __declspec(thread) method we mentioned earlier to define thread private variables, so as to prevent library functions from failing under multi-threading, but uses a _tiddata structure on the heap to make threads private The variable is placed inside the structure, and the pointer to _tiddata is saved by explicit TLS.
After understanding this information, we should think of a problem, that is, if we use CreateThread() to create a thread and then call the CRT's strtok() function, it should be wrong, because the _tiddata required by strtok() does not It exists, but we seem to have never encountered such a problem. Looking at the strtok() function, you will find that when _getptd() is first called to get the _tiddata structure of the thread, if this function finds that the thread has not applied for the _tiddata structure, it will apply for this structure and be responsible for initialization. So no matter which function we call to create the thread, we can safely call all functions that need _tiddata, because once this structure does not exist, it will be created.
So when will _tiddata be released? ExitThread() will definitely not, because it does not know that there is such a structure as _tiddata, so it is obviously released by _endthread(), which is exactly what CRT does. However, we often find that even if we use CreateThread() and ExitThread() (the effect of exiting the thread function directly without calling ExitThread() is the same), we will not find any memory leaks. Why is this? After careful inspection, we found that the original password is in the entry function DllMain of the CRT DLL. We know that when a process/thread starts or exits, the DllMain of each DLL will be called once, so the dynamic link version of the CRT has the opportunity to release the thread's _tiddata in DllMain. But DllMain only works when the CRT is a dynamic link version, and the static link CRT does not have DllMain! This is a situation where using CreateThread() will cause a memory leak. In this case, _tiddata cannot be released when the thread ends, causing a leak.

We can use the following small program to test:

#include <Windows.h>
#include <process.h>
void thread(void *a)
{
    
    
    char* r = strtok( "aaa", "b" );
    ExitThread(0); // 这个函数是否调用都无所谓
}
int main(int argc, char* argv[])
{
    
    
    while(1) {
    
    
        CreateThread(  0, 0, (LPTHREAD_START_ROUTINE)thread, 0, 0, 0 );
        Sleep( 5 );
    }
return 0;
}

If you use a dynamically linked CRT (/MD, /MDd), there will be no problem. However, if you use a statically linked CRT (/MT, /MTd), you will find that the memory usage is not large if you observe it in the process manager after running the program. It keeps rising, but if we change ExitThread() in the thread() function to _endthread(), there will be no problem, because _endthread() will release _tiddata().

This problem can be summarized as: ***When using CRT (basically all programs use CRT), please try to use the _beginthread()/_beginthreadex()/_endthread()/_endthreadex() group of functions to create threads. ***In MFC, there is a group of similar functions AfxBeginThread() and AfxEndThread(). According to the above principle, it is a thread wrapper function at the MFC level. They will maintain the thread and MFC-related structures. When we use When using the MFC class library, try to use the thread wrapper functions it provides to ensure that the program runs correctly.

Reposted from https://www.jb51.net/article/41459.htm

Guess you like

Origin blog.csdn.net/sinat_36391009/article/details/108764133