在学校研究室的时候,刚刚做嵌入式的时候,导师们让我接触多线程的时候,都是使用CreateThread,也许很多朋友和我一样。
最近自己看书的时候却出现显了一些疑问?
引申阅读:
关于_beginthreadex和CreateThread的区别
先来谈谈概念吧!例子这个东西稍后附上!
下面是关于_beginthreadex的一些要点:
•每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的VisualC++源代码中)。
•传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。
•_beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。
•当调用CreatetThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。
•如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。
4) _endthreadex的一些要点:
•C运行期库的_getptd函数内部调用操作系统的TlsGetValue函数,该函数负责检索调用线程的tiddata内存块的地址。
•然后该数据块被释放,而操作系统的ExitThread函数被调用,以便真正撤消该线程。当然,退出代码要正确地设置和传递。
5)虽然也提供了简化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。
6)线程handle因为是内核对象,所以需要在最后closehandle。
7)更多的API:HANDLE GetCurrentProcess();HANDLE GetCurrentThread();DWORD GetCurrentProcessId();DWORD GetCurrentThreadId()。DWORD SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);BOOL SetThreadPriority(HANDLE hThread,int nPriority);BOOL SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);BOOL SwitchToThread();
三注意
1)C++主线程的终止,同时也会终止所有主线程创建的子线程,不管子线程有没有执行完毕。所以上面的代码中如果不调用WaitForSingleObject,则2个子线程t1和t2可能并没有执行完毕或根本没有执行。
2)如果某线程挂起,然后有调用WaitForSingleObject等待该线程,就会导致死锁。所以上面的代码如果不调用resumethread,则会死锁。
关于_beginthreadex和CreateThread的区别我就不做说明了,这个很 容易找到的。我们只要知道一个问题:_beginthreadex是一个C运行时库的函数,CreateThread是一个系统API函 数,_beginthreadex内部调用了CreateThread。只所以所有的书都强调内存泄漏的问题是因为_beginthreadex函数在创 建线程的时候分配了一个堆结构并和线程本身关联起来,我们把这个结构叫做tiddata结构,是通过线程本地存储器TLS于线程本身关联起来。我们传入的 线程入口函数就保存在这个结构中。tiddata的作用除了保存线程函数入口地址之外,还有一个重要的作用就是:C运行时库中有些函数需要通过这个结构来 保存和获取一些数据,比如说errno之类的线程全局变量。这点才是最重要的。
当一个线程调用一个要求tiddata结构的运行时库函数的时候,将发生下面的情况:
运行时库函数试图TlsGetv alue获取线程数据块的地址,如果没有获取到,函数就会 现场分配一个 tiddata结构,并且和线程相关联,于是问题出现了,如果不通过_endthreadex函数来终结线程的话,这个结构将不会被撤销,内存泄漏就会出 现了。但通常情况下,我们都不推荐使用_endthreadex函数来结束线程,因为里面包含了ExitThread调用。
找到了内存泄漏的具体原因,我们可以这样说:只要在创建的线程里面不使用一些要求tiddata结构的运行时库函数,我们的内存时安全的。所以,前面说的那句话应该这样说才完善:
“绝对不要调用系统自带的CreateThread函数创建新的线程,而应该使用_beginthreadex,除非你在线程中绝不使用需要tiddata结构的运行时库函数”
这个需要tiddata结构的函数有点麻烦了,在侯捷的《win32多线程程序设计》一书中这样说到:
”如果在除主线程之外的任何线程中进行一下操作,你就应该使用多线程版本的C runtime library,并使用_beginthreadex和_endthreadex:
1 使用malloc()和free(),或是new和delete
2 使用stdio.h或io.h里面声明的任何函数
3 使用浮点变量或浮点运算函数
4 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()或rand()
那么概念先介绍到这里了,下面将进行小Demo的演示,未完待续!敬请等待!
那么我们先来一个createThrea示例
函数功能:创建线程
函数原型:
HANDLEWINAPICreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes,
SIZE_TdwStackSize,
LPTHREAD_START_ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,
LPDWORDlpThreadId
);
函数说明:
第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。
第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
第四个参数是传给线程函数的参数。
第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。
函数返回值:
成功返回新线程的句柄,失败返回NULL。
MSDN 参考示例:
#include <windows.h> <WINDOWS.H> #include <strsafe.h> <STRSAFE.H> #include <stdio.h><STDIO.H> #define BUF_SIZE 255 //------------------------------------------ // A function to Display the message // indicating in which tread we are //------------------------------------------ void DisplayMessage (HANDLE hScreen, char *ThreadName, int Data, int Count) { TCHAR msgBuf[BUF_SIZE]; size_t cchStringSize; DWORD dwChars; // Print message using thread-safe functions. StringCchPrintf(msgBuf, BUF_SIZE, TEXT("Executing iteration %02d of %s" " having data = %02d \n"), Count, ThreadName, Data); StringCchLength(msgBuf, BUF_SIZE, &cchStringSize); WriteConsole(hScreen, msgBuf, cchStringSize, &dwChars, NULL); Sleep(1000); } //------------------------------------------- // A function that represents Thread number 1 //------------------------------------------- DWORD WINAPI Thread_no_1( LPVOID lpParam ) { int Data = 0; int count = 0; HANDLE hStdout = NULL; // Get Handle To screen. // Else how will we print? if( (hStdout = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE ) return 1; // Cast the parameter to the correct // data type passed by callee i.e main() in our case. Data = *((int*)lpParam); for (count = 0; count <= 4; count++ ) { DisplayMessage (hStdout, "Thread_no_1", Data, count); } return 0; } //------------------------------------------- // A function that represents Thread number 2 //------------------------------------------- DWORD WINAPI Thread_no_2( LPVOID lpParam ) { int Data = 0; int count = 0; HANDLE hStdout = NULL; // Get Handle To screen. Else how will we print? if( (hStdout = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE ) return 1; // Cast the parameter to the correct // data type passed by callee i.e main() in our case. Data = *((int*)lpParam); for (count = 0; count <= 7; count++ ) { DisplayMessage (hStdout, "Thread_no_2", Data, count); } return 0; } //------------------------------------------- // A function that represents Thread number 3 //------------------------------------------- DWORD WINAPI Thread_no_3( LPVOID lpParam ) { int Data = 0; int count = 0; HANDLE hStdout = NULL; // Get Handle To screen. Else how will we print? if( (hStdout = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE ) return 1; // Cast the parameter to the correct // data type passed by callee i.e main() in our case. Data = *((int*)lpParam); for (count = 0; count <= 10; count++ ) { DisplayMessage (hStdout, "Thread_no_3", Data, count); } return 0; } void main() { // Data of Thread 1 int Data_Of_Thread_1 = 1; // Data of Thread 2 int Data_Of_Thread_2 = 2; // Data of Thread 3 int Data_Of_Thread_3 = 3; // variable to hold handle of Thread 1 HANDLE Handle_Of_Thread_1 = 0; // variable to hold handle of Thread 1 HANDLE Handle_Of_Thread_2 = 0; // variable to hold handle of Thread 1 HANDLE Handle_Of_Thread_3 = 0; // Aray to store thread handles HANDLE Array_Of_Thread_Handles[3]; // Create thread 1. Handle_Of_Thread_1 = CreateThread( NULL, 0, Thread_no_1, &Data_Of_Thread_1, 0, NULL); if ( Handle_Of_Thread_1 == NULL) ExitProcess(Data_Of_Thread_1); // Create thread 2. Handle_Of_Thread_2 = CreateThread( NULL, 0, Thread_no_2, &Data_Of_Thread_2, 0, NULL); if ( Handle_Of_Thread_2 == NULL) ExitProcess(Data_Of_Thread_2); // Create thread 3. Handle_Of_Thread_3 = CreateThread( NULL, 0, Thread_no_3, &Data_Of_Thread_3, 0, NULL); if ( Handle_Of_Thread_3 == NULL) ExitProcess(Data_Of_Thread_3); // Store Thread handles in Array of Thread // Handles as per the requirement // of WaitForMultipleObjects() Array_Of_Thread_Handles[0] = Handle_Of_Thread_1; Array_Of_Thread_Handles[1] = Handle_Of_Thread_2; Array_Of_Thread_Handles[2] = Handle_Of_Thread_3; // Wait until all threads have terminated. WaitForMultipleObjects( 3, Array_Of_Thread_Handles, TRUE, INFINITE); printf("Since All threads executed" " lets close their handles \n"); // Close all thread handles upon completion. CloseHandle(Handle_Of_Thread_1); CloseHandle(Handle_Of_Thread_2); CloseHandle(Handle_Of_Thread_3); } beginthreadex Demo class MyClass { _ beginthreadex(NULL,0, function, 0,0,0); } int main () { MyClass RunOldMain; return; } void function(LPWSTR lpParam ) { // function is plain 'C' return; }