1、CreateThread、_beginthreadex、AfxBeginThread的区别和正确使用:

CreateThread是一个Windows的API函数,_beginthreadex是一个微软VC中C运行时库中的线程创建函数,AfxBeginThread则是MFC中的线程创建函数。

其依赖关系为:<--表示被依赖

CreateThread <--_beginthreadex

CreateThread <-- AfxBeginThread

_beginthreadex为每个使用线程在Heap上创建(用__calloc_crt,相当于calloc)了一个tiddata结构并且设定到动态TLS。这样C运行时库中使用静态变量的几个函数就可以得到只和线程相关的一份“静态”和“全局”变量了,C运行时全局变量也不会互相干扰。假若使用CreateThread创建了这同样的线程,因为没有事先分配这个tiddata结构,那么后果自然就很严重了。

但是使用了_beginthreadex也有一个不算麻烦的小麻烦。

线程最好的退出方式是从return退出,所有的资源都会正确的释放,如果调用ExitThread退出线程,那么线程堆栈上的内容被自动清除,但是C++对象不能够通过调用析构函数而正确的清除。而和_beginthreadex对应的_endthreadex恰恰就是调用了ExitThread来终止线程。所以,这下_endthreadex也不能调用了。那么在前面分配的tiddata怎么办?没有任何办法,除非去调用_endthreadex才会得到释放,但是ExitThread语句又紧跟在后边。还好,tiddata结构非常小,不超过100个字节,但是要是遇上傻大黑粗的野蛮程序员,在进程生命期中反复创建线程,也挺恐怖。

所以最好的做法就是,在线程中,避免使用C运行时函数,用C++运行时库或者Windows API解决问题。这样就可以避免使用_beginthreadex,而用CreateThread创建线程,也不会有tiddata结构的运行时内存泄漏了。(或者,不在线程中使用C++对象,这样就不怕_endthreadex的ExitThread 了。 馊主意!不过_beginthreadex本来就是为使用C函数的线程准备的。)

至于AfxBeginThread,是MFC中定义的一个函数,如果你使用MFC框架,那么应该使用这个函数,这样更符合框架的设计目的。这个函数内部的实现就是通过CreateThread,因为既然都使用MFC了,还费劲使用C函数库干嘛?

2、CreateThread的不知道算不算Bug的Bug:

CreateThread的定义请参见不同版本的MSDN。

这个地址是VS2008 MSDN中的定义 http://msdn.microsoft.com/en-us/library/ms682453(VS.85).aspx

该API函数的第2个参数 dwStackSize 以及 第5个参数 dwCreationFlag分别指定了线程堆栈的大小和创建标志。

按照文档中的说明,dwCreationFlag有一个设定值为STACK_SIZE_PARAM_IS_A_RESERVATION。这个标志的含义是,将指定大小的堆栈在虚拟内存中分配,并且初始只映射系统缺省页数的物理内存(大概是两个页),随着堆栈使用的增大,系统根据堆栈物理内存页后方的设定为PAGE_GUARD属性的页触发来把更多的物理内存映射到后续的页。

值得注意的是,STACK_SIZE_PARAM_IS_A_RESERVATION这个标志只在WindowsXP以及Windows 2003以及更新的系统上才会得到支持。如果没有设定这个标志,并且也让线程一创建就开始运行(大部分时候是这样),那么系统会参照dwStackSize中指定的堆栈大小,并参照内存分配最小粒度以及页对齐原则,把一定大小的物理内存映射给堆栈。

看上去一切都很不错,但是如果你尝试创建一个会很快耗光堆栈的线程ThreadProc,并且用下面的语句创建:

CreateThread(NULL, 1024 * 1024 , ThreadProc, NULL, 0,&threadId);

然后在线程中精心设置好异常捕捉,满心欢喜的等待EXCEPTION_STACK_OVERFLOW的到来。

那么你会震惊的发现:

程序因为EXCEPTION_ACCESS_VIOLATION异常而直接退出了。没有EXCEPTION_STACK_OVERFLOW。

你重新设置异常条件,决定捕捉全部的异常,但是你会发现你完全无法捕捉。Why?

接下来,你把堆栈大小设置为:1024 * 1024 + 1 或者1024 * 1024 -8092,那么又可以准确的捕捉到溢出异常了。

或者你把dwStackSize设置为0,或者给dwCreationFlag加上STACK_SIZE_PARAM_IS_A_RESERVATION标志,也可以成功捕捉异常。

现在应该知道原因了。运行在x86上的Windows XP,目前内存分配最小粒度为64KB,用户态下页面大小是4KB。

当把dwStackSize设置为0时,系统会缺省设置堆栈为1MB大小,但是事实上线程得不到1MB的堆栈,因为系统会在堆栈最后的部分用加上PAGE_GUARD属性的页来检测溢出,并且还要保留用于处理溢出异常发生时的页面,所以可以检测到异常。

而给dwCreationFlag加上STACK_SIZE_PARAM_IS_A_RESERVATION标志时,按照64KB分配粒度的原则,堆栈最后的部分始终有足够的页来检测溢出和处理异常。所以也很安全。

而为线程指定1024 *1024大小堆栈时,堆栈边界正好和64KB以及1MB边界对齐的,当完全提交物理内存时,堆栈的所有页的属性都将是PAGE_READWRITE(事实上应该是一个Windows的Bug,但是考虑性能原因而故意让其存在)。所以系统根本没机会检测到溢出错误,而会在堆栈溢出后直接引发EXCEPTION_ACCESS_VIOLATION。而因为没有预留的异常处理页面,异常处理程序又会引发一个EXCEPTION_ACCESS_VIOLATION异常。天啦!!整个进程就这样崩溃了。

而为什么指定1024 * 1024 + 1 或者1024 * 1024 -8092就可以捕捉异常了呢?因为堆栈大小在超过1MB后,是按照1MB对齐原则,哪怕多一个字节,系统也会多映射1MB的物理内存。这样足够两页的异常检测和异常处理页面。1024* 1024 - 8092大小正是这个原因所以能正确引发异常。

所以,正确的做法是,如果想在运行CreateThread时指定堆栈大小,并且全部提交物理内存的话,那么大小必须保证在整1MB边界上留出至少两个页面给系统。

CreateThread、_beginthreadex、AfxBeginThread相关推荐

  1. CreateThread、_beginthreadex和AfxBeginThread 的区别

    CreateThread._beginthreadex和AfxBeginThread 创建线程好几个函数可以使用,可是它们有什么区别,适用于什么情况呢? 参考了一些资料,写得都挺好的,这里做一些摘抄和 ...

  2. 【读书笔记】【程序员的自我修养 -- 链接、装载与库(三)】函数调用与栈(this指针、返回值传递临时对象构建栈、运行库与多线程、_main函数、系统调用与中断向量表、Win32、可变参数、大小端

    文章目录 前言 介绍 内存 内存布局 栈与调用惯例 堆与内存管理 运行库 入口函数和程序初始化 C/C++运行库 运行库与多线程 C++全局构造与析构 fread 实现 系统调用与API 系统调用介绍 ...

  3. _beginthreadex、CreateThread、AfxBeginThread的选择

      _beginthreadex.CreateThread.AfxBeginThread的选择 收藏 1.  Create/EndThread是Win32方法开始/结束一个线程 _beginthrea ...

  4. CreateThread、_beginthread与AfxBeginThread的区别及其注意事项

    在windows下一般由以下三种方式来进行线程操作: 1.通过CreateThread(),对应线程结束函数ExitThread(): 2.通过_beginthread(),对应线程结束函_endth ...

  5. 关于_beginthreadex、_beginthread和CreateThread

    关于_beginthreadex._beginthread和CreateThread 在微软的 Programming Techniques 说明文件中有一句看似悲惨的警告: 警告:如果你在一个与 L ...

  6. MFC之AfxbeginThread 线程 创建、挂起、释放、结束、退出

    MFC之AfxbeginThread 线程 创建.挂起.释放.结束.退出 本文简单阐述了如何使用一个afxbeginthread创建一个线程(两种方法,使用默认设置,使用自主设置参数),以及一些如同, ...

  7. CreateThread 和_beginthreadex区别

    本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...

  8. 多线程之 CreateThread与_beginthreadex本质区别

    本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...

  9. 【转】windows多线程CreateThread与_beginthreadex本质区别

    本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...

最新文章

  1. python list的extend (会将被插入的列表的每个元素从列表中拿出添加到列表中)与append方法(若被插入为列表,会将列表插入到源列表中)区别
  2. PHP——获取上传文件的后缀名
  3. JAVA标识符中含小数点可以吗_数值类型小数点后是否可以接零问题
  4. html按钮返回上一步操作,用js实现返回上一步操作
  5. 6月份美国域名总量新增近5.4万个 环比减少51%
  6. 邮政计算机网络,邮政计算机网络论文(共2018字).doc
  7. 值得关注的AI信息安全公司
  8. python3 json_python3 json模块
  9. lightoj1027_数学求期望
  10. sqli注入前置知识
  11. VIM 下工程的管理工具
  12. 超简单的内网邮件服务器搭建(CentOS7 postfix+dovecot)
  13. Algorithm:数学建模大赛(CUMCM/NPMCM)之全国大学生数模竞赛简介 相关书籍、文章推荐等详细攻略
  14. 51 TMOD、TCON设置定时
  15. CSS精灵优化Retina显示屏下的网站图像
  16. Instagram移动网页版推图片分享功能:追求国际增长
  17. 【网络】能远程电脑,但ping不通
  18. ZigBee智能节水灌溉系统
  19. java如何直接打印数组
  20. Leetcode-1658. 将 x 减到 0 的最小操作数

热门文章

  1. 深入SecureFile—新一代LOB揭秘000
  2. 【对讲机的那点事】车载台天线系统故障的检测、排除方法(上)
  3. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解
  4. BeagleBone Black Industrial 进阶设置(性能优化以及延长板载eMMC存储寿命)
  5. 成功企业的核心思维逻辑
  6. php-fpm 参数及配置详解
  7. 收集Oracle常用命令----索引及约束
  8. [转载]日历设计之重复事件规则设计
  9. LindDotNetCore~Mock对实际应用中的意义
  10. JS魔法堂:IE5~9的DragDrop API