zoukankan      html  css  js  c++  java
  • (转)CreateThread与_beginthread,内存泄漏为何因(原帖排版有些不好 ,所以我稍微整理下)

            在写c++代码时,一直牢记着一句话:决不应该调用CreateThread。 应该使用Visual   C++运行时库函数_beginthreadex。好像CreateThread函数就是老虎,既然这样为什么微软要开发这个函数呢?
     
     

           不要用 CreateThread 创建线程、并用 CloseHandle 来关闭这个线程,因为这样会导致内存泄露,而应该用 _beginthread 来创建线程,_endthread 来销毁线程。其实,真正的原因并非如此

           因为CreateThread 后,线程终止运行后,线程对象仍然在系统中,必须通过 CloseHandle 函数来关闭该线程对象。为什么会引起内存泄露呢?因为当线程的函数用到了C的标准库的时候,很容易导致冲突,所以在创建VC的工程时,系统提示是用单线程还是用多线程的库,因为在C的内部有很多的全局变量。例如 errno。


           因为在C的库中有全局变量,如果程序中使用了标准的C的库时,很容易导致运行不正常。所以,微软和Borland都对C的库进行了一些改进。但是这个改进的一个条件就是,如果一个线程已经开始创建,就应该创建一个结构来包含这些全局变量,接着把这些全局变量放入线程的上下文中。从而全局变量就会依赖于这个线程,不会引起冲突。


           这样做就会有一个问题,什么时候这个线程开始创建呢?标准的C库是不知道的,而 CreateThread 是操作系统的API。所以需要使用 _beginthread/_endthread 来创建/结束线程,让标准C库为线程化做些准备工作。


             当用 _beginthread 来创建,而用 CloseHandle 来关闭线程时,标准C库复制的全局变量就不会被释放,这才是前面说的内存泄露的原因。


              另一方面,如果用 CreateThread/CloseHandle 来创建/结束线程,则不要使用标准C库的任何函数。还有一个需要注意的,就是在线程执行完之前,不要使用 CloseHandle 来结束线程,否则也会有异常。我们一般通过 WaitForSingleObject/WaitForMultipleObjects 来等待线程结束。

     
     
         CreateThread函数是用来创建线程的Windows函数。不过,如果你正在编写C/C++代码,决不应该调用CreateThread。相反,应该使用Visual   C++运行期库函数_beginthreadex。如果不使用Microsoft的Visual   C++编译器,你的编译器供应商有它自己的CreateThred替代函数。   
         若要使多线程C和C++程序能够正确地运行,必须创建一个数据结构,并将它与使用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值。
     
      
           CreateThread、_beginthread和_beginthreadex都是用来启动线程的,但大家看到oldworm没有提供_beginthread的方式,原因简单,_beginthread是_beginthreadex的功能子集,虽然_beginthread内部是调用_beginthreadex但他屏蔽了象安全特性这样的功能,所以_beginthread与CreateThread不是同等级别,_beginthreadex和CreateThread在功能上完全可替代,我们就来比较一下_beginthreadex与CreateThread!   
        
          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的函数在多线程的情况下必须有特殊的支持,不能简单的使用CreateThread就OK。   
      大多的CRT函数都可以在CreateThread线程中使用,看资料说只有signal()函数不可以,会导致进程终止!但可以用并不是说没有问题!  
        
      有些CRT的函数象malloc(),   fopen(),   _open(),   strtok(),   ctime(),   或localtime()等函数需要专门的线程局部存储的数据块,这个数据块通常需要在创建线程的时候就建立,如果使用CreateThread,这个数据块就没有建立,然后会怎样呢?在这样的线程中还是可以使用这些函数而且没有出错,实际上函数发现这个数据块的指针为空时,会自己建立一个,然后将其与线程联系在一起,这意味着如果你用CreateThread来创建线程,然后使用这样的函数,会有一块内存在不知不觉中创建,遗憾的是,这些函数并不将其删除,而CreateThread和ExitThread也无法知道这件事,于是就会有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来释放结构。

     

    观众看点:

    当你打算实现一个多线程(非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.

    ××××××××××××××××××××××××××××××××××××××

    说简单点,当线程异常结束的时候,_beginthread创建的线程会调用栈上变量的析构函数,释放资源,而CreateThread创建的线程不会。因为CreateThread是操作系统提供的接口,要效率,要速度,他不知道堆栈这玩意,无心处理这些,也许某种语言的内存模式跟C/C++完全不一样,所以CreateThread不会去管C/C++的东西;但CRT为了保证一致性,C/C++变量的析构是他的责任,所以CRT得管,封装了强化了CreateThread,保证一致性。

    ××××××××××××××××××××××××××××××××××××××××××××××××××

    具体说来,CreateThread这个 函数是windows提供给用户的 API函数,是SDK的标准形式,在使用的过程中要考虑到进程的同步与互斥的关系,进程间的同步互斥等一系列会导致操作系统死锁的因素,用起来比较繁琐一些,初学的人在用到的时候可能会产生不可预料的错误,建议多使用AfxBeginThread,是编译器对原来的CreateThread函数的封装,用与MFC编程(当然,只要修改了项目属性,console和win32项目都能调用)而_beginthread是C的运行库函数。

    在使用AfxBeginThread时,线程函数的定义为:UINT _yourThreadFun(LPVOID pParam)参数必须如此
    在使用CreateThread时,线程的函数定义为: DWORD WINAPI _yourThreadFun(LPVOID pParameter)。

    两个的实质都是一样的,不过AfxBeginThread返回一个CWinThread的指针,就是说他会new一个CWinThread对象,而且这个对象是自动删除的(在线程运行结束时),给我们带来的不便就是无法获得它的状态,因为随时都有可能这个指针指向的是一个已经无效的内存区域,所以使用时(如果需要了解它的运行状况的话)首先CREATE_SUSPENDED让他挂起,然后m_bAutoDelete=FALSE,接着才ResumeThread,最后不要了delete那个指针。
    CreatThread就方便多了,它返回的是一个句柄,如果你不使用CloseHandle的话就可以通过他安全的了解线程状态,最后不要的时候CloseHandle,Windows才会释放资源,所以我一般使用CreatThread,方便。

    如果用MFC编程,不要用CreateThread,如果只是使用Runtime Library,用_BegingThread,总之,不要轻易使用CreateThread。这是因为在MFC和RTL中的函数有可能会用到些它们所封装的公用变量,也就是说AfxBeginThread和_BeginThread都有自己的启动代码是CreateThread所没有的。
    在用CreateThread所创建的线程中使用MFC的类和RTL函数就有可能出现问题。
    如果你是用汇编编写win32程序并且在线程函数中也不调用MFC和RTL的函数,那用CreateThread就没问题,或者你虽然是用C写线程函数,但你很小心没调用RTL函数也不会有问题。
    CreateThread是由操作系统提供的接口,而AfxBeginThread和_BeginThread则是编译器对它的封装。

    在可能的情况下,不要调用_beginthread,而应该调用_beginthreadex,以及对应的_endthreadex。这都是C++运行期函数。
    但是使用_beginthread,无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程ID,_endthread的情况类似,它不带参数,

    这意味这线程的退出代码必须硬编码为0。这两个函数在_beginthreadex和_endthreadex中进行调用。CreateThread不要进行直接调用。

  • 相关阅读:
    Delphi XE5 android 蓝牙通讯传输
    Delphi XE5 android toast
    Delphi XE5 android openurl(转)
    Delphi XE5 如何设计并使用FireMonkeyStyle(转)
    Delphi XE5 android 捕获几个事件
    Delphi XE5 android listview
    Delphi XE5 android 黑屏的临时解决办法
    Delphi XE5 android popumenu
    Delphi XE5 android 获取网络状态
    Delphi XE5 android 获取电池电量
  • 原文地址:https://www.cnblogs.com/wenluderen/p/4246075.html
Copyright © 2011-2022 走看看