CreateThread与_beginthread,内存泄漏为何因

在写 c++代码时,一直牢记着一句话:决不应该调用 CreateThread。相反,应该使用 Visual   C++运行期库函数 _beginthreadex
好像 CreateThread函数就是老虎,既然这样为什么微软要开发这个函数呢?
从网上找到的相关资料,现在汇总一下,在此对相关人员进行感谢!
 
摘自《 windows   核心编程》:    
     CreateThread
函数是用来创建线程的 Windows函数。不过,如果你正在编写 C/C++代码,决不应该调用 CreateThread。相反,应该使用 Visual   C++运行期库函数 _beginthreadex。如果不使用 MicrosoftVisual   C++编译器,你的编译器供应商有它自己的 CreateThred替代函数。    
     
若要使多线程 CC++程序能够正确地运行,必须创建一个数据结构,并将它与使用 C/C++运行期库函数的每个线程关联起来。当你调用 C/C++运行期库时,这些函数必须知道查看调用线程的数据块,这样就不会对别的线程产生不良影响。    
   1.
每个线程均获得由 C/C++运行期库的堆栈分配的自己的 tiddata内存结构。    
   2.
传递给 _beginthreadex的线程函数的地址保存在 tiddata内存块中。传递给该函数的参数也保存在该数据块中。    
   3._beginthreadex
确实从内部调用 CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。    
   4.
当调用 CreatetThread时,它被告知通过调用 _threadstartex而不是 pfnStartAddr来启动执行新线程。       还有,传递给线程函数的参数是 tiddata结构而不是 pvParam的地址。    
5.如果一切顺利,就会像 CreateThread那样返回线程句柄。如果任何操作失败了,便返回  NULL  

     _beginthreadex
_beginthread函数的区别。 _beginthread函数的参数比较少,因此比特性全面的 _beginthreadex函数受到更大的限制。    
  
例如,如果使用 _beginthread,就无法创建带有安全属性的新线程,无法创建暂停的线程,也  无法获得线程的 ID值。
 
下面摘录 Csdn中的 Holly()的帖子进行解释,再次表示感谢。
来源: [url]http://topic.csdn.net/t/20000926/10/31810.html[/url]
Holly():
oldworm提供了很好的使用的例子,而且也运用了编译控制!    
 
我来解释一下理论上的区别:    
 CreateThread
_beginthread_beginthreadex都是用来启动线程的,但大家看到 oldworm没有提供 _beginthread的方式,原因简单, _beginthread_beginthreadex的功能子集,虽然 _beginthread内部是调用 _beginthreadex但他屏蔽了象安全特性这样的功能,所以 _beginthreadCreateThread不是同等级别, _beginthreadexCreateThread在功能上完全可替代,我们就来比较一下 _beginthreadexCreateThread!   
    
  CRT
的函数库在线程出现之前就已经存在,所以原有的 CRT不能真正支持线程,这导致我们在编程的时候有了 CRT库的选择,在 MSDN中查阅 CRT的函数时都有:    
  Libraries   
  LIBC.LIB   Single   thread   static   library,   retail   version     
  LIBCMT.LIB   Multithread   static   library,   retail   version     
  MSVCRT.LIB   Import   library   for   MSVCRT.DLL,   retail   version     
  
这样的提示!    
  
对于线程的支持是后来的事!    
  
这也导致了许多 CRT的函数在多线程的情况下必须有特殊的支持,不能简单的使用 CreateThreadOK   
  
大多的 CRT函数都可以在 CreateThread线程中使用,看资料说只有 signal()函数不可以,会导致进程终止!但可以用并不是说没有问题!   
    
  
有些 CRT的函数象 malloc(),   fopen(),   _open(),   strtok(),   ctime(),   localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用 CreateThread,这个数据块就没有建立,然后会怎样呢?在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用 CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而 CreateThreadExitThread也无法知道这件事,于是就会有 Memory   Leak,在线程频繁启动的软件中 (比如某些服务器软件 ),迟早会让系统的内存资源耗尽!    
    
  _beginthreadex(
内部也调用 CreateThread)_endthreadex就对这个内存块做了处理,所以没有问题! (不会有人故意用 CreateThread创建然后用 _endthreadex终止吧,而且线程的终止最好不要显式的调用终止函数,自然退出最好! )   
    
  
谈到 Handle的问题, _beginthread的对应函数 _endthread自动的调用了 CloseHandle,而 _beginthreadex的对应函数 _endthreadex则没有,所以 CloseHandle无论如何都是要调用的不过 _endthread可以帮你执行自己不必写,其他两种就需要自己写! (Jeffrey   Richter强烈推荐尽量不用显式的终止函数,用自然退出的方式,自然退出当然就一定要自己写 CloseHandle)

线程的内存泄漏的主要原因

 

 在很多参考书上,都说不要用CreateThread 创建线程、并用CloseHandle来关闭这个线程,因为这样做会导致内存泄漏,而应该用_beginthread来创建线程,_endthread来销毁线程。其实,真正的原因并非如此。看如下一段代码:  HANDLE CreateThread(  LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性  DWORD dwStackSize, // 堆栈大小  LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数  LPVOID lpParameter, //线程参数  DWORD dwCreationFlags, // 线程创建属性  LPDWORD lpThreadId // 线程ID  );  线程中止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。CloseHandle函数的原型是:  BOOL CloseHandle( HANDLE hObject );//HANDLE hObject 对象句柄  CloseHandle可以关闭多种类型的对象,比如文件对象等,这里使用这个函数来关闭线程对象。调用时,hObject为待关闭的线程对象的句柄。  说用这种方法时内存在泄漏,其实不完全正确。那为什么会引起内存的泄漏呢?因为当线程的函数用到了C的标准库的时候,很容易导致冲突,所以在创建VC的工程时,系统提示是用单线程还是用多线程的库,因为在C的内部有很多的全局变量。例如,出错号、文件句柄等全局变量。  因为在C的库中有全局变量,这样用C的库时,如果程序中使用了标准的C的库时,就很容易导致运行不正常,会引起很多的冲突。所以,微软和Borland都对C的库进行了一些改进。但是这个改进的一个条件就是,如果一个线程已经开始创建了,就应该创建一个结构来包含这些全局变量,接着把这些全局变量放入线程的上下文中和这个线程相关起来。这样,全局变量就会依赖于这个线程,不会引起冲突。  这样做就会有一个问题,什么时候这个线程开始创建呢?标准的Windows的API是不知道的,因为它是静态的库。这些库都是放在VC的LIB的目录内的,而线程函数是操作系统的函数。所以,VC和BC在创建线程时,都会用_beginThread来创建线程,再用_endThread来结束线程。这样,它们在创建线程的时候,就会知道什么时候创建了线程,并把全局变量放入某一结构中,让它和线程能关联起来。这样就不会发生冲突了。  很显然,要完成这个功能,首先需要分配结构表把全局变量包含起来。这个过程是在_beginThread时做的,而释放在_endTread内完成。  所以,当用_beginThread来创建,而用CloseHandle来关闭线程时,这时复制的全局结构就不会被释放了,这就有了内存的泄漏。这就是很多资料所说的内存泄漏问题的真正的原因。  其实,可以不用_beginThread和_endThread这一对函数。如果用CreateThread函数创建,用CloseHandle关闭,那么,与C有关的库就会用全局的,它们会引起冲突。所以,比较好的方法就是在线程内不用标准的C的库(可以使用Windows API的库函数)。这样就不会有什么问题,也就不会引起冲突。例如,字符串的操作函数、文件操作等。  当某个程序创建一个线程后,会产生一个线程的句柄,线程的句柄主要用来控制整个线程的运行,例如停止、挂起或设置线程的优先级等操作。一般来说,当线程启用后,就会用线程的CloseHandle来关闭线程。但在微软的示例程序中,有一个例子创建以后,就马上调用CloseHandle关闭线程的运行。这样做在Windows 98下没什么问题,但在Windows NT下,内核就会出现错误。这是为什么呢?  这是因为虽然线程有关的结构已经释放了,但线程还在运行中,所以程序就会出现错误。那怎么做才能确保正常运行呢?  其实,要正常运行,可以让线程完全结束以后,再调用CloseHandle来释放资源。  怎样知道线程完全结束呢?在Windows 的API中有一类等待线程的命令:  DWORD WaitForSingleObject(  HANDLE hHandle, // handle to object to wait for  DWORD dwMilliseconds , // time-out interval in milliseconds  );  DWORD WaitForMultipleObjects(  DWORD nCount, // number of handles in the handle array  CONST HANDLE *lpHandles, // pointer to the object-handle array  BOOL fWaitAll, // wait flag  DWORD dwMilliseconds // time-out interval in milliseconds  );  可以用以上两函数,等待线程的结束。如果线程结束,函数就会返回。否则就一直等待,直到指定的时间结束。  还有一种线程根本不会退出,它一直运行着循环的线程。我们就要用中止线程的方法来结束线程的运行,强制把它关闭。强制关闭后,再用CloseHandle来释放结构。

观众看点:
为何要用_beginthreadex()而非CreateThread?

当你打算实现一个多线程(非MFC)程序,你会选择一个单线程的CRT(C运行时库)吗?如果你的回答是NO, 那么会带来另外一个问题,你选择了CreateThread来创建一个线程吗? 大多数人也许会立刻回答YES。可是很不幸,这是错误的选择。
我先来说一下我的结论,待会告诉你为什么。

如果要作多线程(非MFC)程序,在主线程以外的任何线程内
- 使用malloc(),free(),new
- 调用stdio.h或io.h,包括fopen(),open(),getchar(),write(),printf(),errno
- 使用浮点变量和浮点运算函数
- 调用那些使用静态缓冲区的函数如: asctime(),strtok(),rand()等。
你就应该使用多线程的CRT并配合_beginthreadex(该函数只存在于多线程CRT), 其他情况下你可以使用单线程的CRT并配合CreateThread。

因为对产生的线程而言,_beginthreadex比之CreateThread会为上述操作多做额外的簿记工作,比如帮助strtok()为每个线程准备一份缓冲区。
然而多线程程序极少情况不使用上述那些函数(比如内存分配或者io),所以与其每次都要思考是要使用_beginthreadex还是CreateThread,不如就一棍子敲定_beginthreadex。

当然你也许会借助win32来处理内存分配和Io,这时候你确实可以以单线程crt配合CreateThread,因为io的重任已经从crt转交给了win32。这时通常你应该使用HeapAlloc,HeapFree来处理内存分配,用CreateFile或者GetStdHandle来处理Io。

还有一点比较重要的是_beginthreadex传回的虽然是个unsigned long,其实是个线程Handle(事实上_beginthreadex在内部就是调用了CreateThread),所以你应该用CloseHandle来结束他。千万不要使用ExitThread()来退出_beginthreadex创建的线程,那样会丧失释放簿记数据的机会,应该使用_endthreadex.

你可能感兴趣的:(多线程,windows,Microsoft,library,Borland,attributes)