在用visual studio进行界面编程时(如MFC),前台UI我们可以通过MFC的消息循环机制实现。而对于后台的数据处理,我们可能会用到多线程来处理。那么对于大多数人(尤其是我这种菜鸟),一个比较快捷的方法便是选择MFC多线程:AfxBeginThread或者CreateThread来进建立多线程。当一两个线程还是可以得,当有3个或者3个以上的线程出现时,极可能出现内存泄漏。原因分析如下:
CWinThread的多线程不安全性:
因为 CWinThread 会调用_beginthreadex来初始化C运行时库,而同样地,如果线程被强制终止(TerminateThread),因为 TerminateThread是不会去管 C运行时库的,从而,导致部分和引用计数相关的C运行时数据的内存释放出现问题。最典型的特征是,使用STL库的静态变量
内存回收将出错,从而导致进程退出时误报异常。
此外,如果AfxBeginThread频繁进行回收和分配线程,如果不严格操作,也会导致崩溃。VC6中,应该严格控制STL库的使用,避免MFC库和STL库并存,否则,会有很多问题。
原因:
AfxBeginThread在内部直接调用了CreateThread创建线程而不是c语言下推荐的beginthreadex函数,而这两个函数是有区别的,主要是c运行库的历史遗留问题造成的。
在多线程环境中存在问题的C/C++运行期库变量和函数包括errno、_doserrno、strtok、_wcstok、strerror、_strerror、tmpnam、tmpfile、asctime、_wasctime、gmtime、_ecvt和_fcvt等。
若要使多线程C/C++程序能够正确地运行,必须创建一个数据结构,并将它与使用C/C++运行期库函数的每个线程关联起来。当你调用C / C + +运行期库时,这些函数必须知道查看调用线程的数据块,这样就不会对别的线程产生不良影响。那么系统是否知道在创建新线程时分配该数据块呢?回答是它不知道。系统根本不知道你得到的应用程序是用C/C++编写的,也不知道你调用函数的线程本身是不安全的。问题在于你必须正确地进行所有的操作。若要创建一个新线程,绝对不要调用操作系统的CreateThread函数,必须调用C/C++运行期库函数_beginthreadex。
下面是关于_beginthreadex的一些要点:
每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。(tiddata结构位于Mtdll.h文件中的Visual C++源代码中)。传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。_beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。###可以看出调用_beginthreadex时分配了额外的内存空间。
如果调用CreateThread,而不是调用C / C + +运行期库的_beginthreadex来创建新线程,将会发生什么情况。当一个线程调用要求tiddata结构的C / C + +运行期库函数时,将会发生下面的一些情况(大多数C / C + +运行期库函数都是线程安全函数,不需要该结构)。首
先, C / C + +运行期库函数试图(通过调用TlsGetValue)获取线程的数据块的地址。如果返回
NULL作为tiddata块的地址,调用线程就不拥有与该地址相关的tiddata块。这时,C / C + +运行期库函数就在现场为调用线程分配一个tiddata块,并对它进行初始化。然后该tiddata块(通过TlsSetValue)与线程相关联。
###_beginthreadex相对应的推出函数是_endthreadex,这个函数会释放tiddata的内容。
---------------------------
如果你采用CreateThread创建线程,而你没有使用上面所提到的那些特殊运行期库的话,也是不会出现问题的;如果一定要用那些运行库的话就最好调用_beginthreadex 这个api来创建线程,否则tiddata数据块就无法撤销,引起内存泄漏。
你可以参照_beginthreadex源代码来进行理解。
引申阅读:
关于_beginthreadex和CreateThread的区别