关于这两个函数的区别,可以参考《Windows 核心编程(第五版)》的第六章 "线程基础",这篇文章的思想多数来源于此,我只是作了一些整理。

线程对于初学者还说可能觉得很高深,这可以理解。对于某些有经验的程序员来说,可能觉得又太简单,我觉得如果认为线程很简单的人,都是没有理解线程,线程里面涉及的东西太多,包括内存,初始化,线程同步等。我打算以QA的形式来写这篇文章。

Q:为什么书上说要以_beginthreadex来替代CreateThread?

A:好了,一直用API CreateThread来创建线程的同志们要注意了,你可能会说我一直用这个API来创建线程,工作刚刚的,一点问题都没有。如果真是这样的话,我只能说是你运气太好了。在_beginthreadex的内部,它调用了CreateThread来创建线程,Windows始终用CreateThread来创建线程。在调用CreateThread之前,beginthreadex它做了很多初始化的工作,所以它比CreateThread创建的线程更加安全。

Q:为什么要用两个相同功能的函数来对待单线程和多线程序程序呢?

A:这里就有一定的历史原因了,标准C语言的库在是1970年左右发明的,而在那时候,线程的概念尚未出现在任何一个操作系统上。但是,线程毕竟是出现了,让我们来看看下面这个例子,来说明以前的CRT为什么不支持多线程:

[cpp] view plaincopy
  1. BOOL fFailure = (system("NOTEPAD.EXE README.TXT") == -1);
  2. if (fFailure)
  3. {
  4. switch (errno)
  5. {
  6. case E2BIG:
  7. // Argument list or environment too big
  8. break;
  9. case ENOENT:
  10. // Command interpreter cannot be found
  11. break;
  12. case ENOEXEC:
  13. // Command interpreter has bad format
  14. break;
  15. case ENOMEM:
  16. // Insufficient memory to run command
  17. break;
  18. }
  19. }

假如代码是这种情况,当执行到"system"这个函数之后,if之前,操作系统把当时CPU时钟周期分配给另一个线程,而在另一个线程中正好使用了会设置errno(这是C语言的一个全局变量)的CRT函数,于是问题就出现了。

所以早期的CRT函数是没有考虑到多线程的,在多线程中还会出问题的CRT函数还有:strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, asctime, etc. 为了保证C和C+多线程应用程序正常运行,必须创建一个数据结构,并使之与使用了C/C+运行库函数的每个线程相关联,然后在调用CRT函数时,那些函数必须知道去查找主调线程的数据块,从而避免影响到其他线程。

那么,当系统创建线程时,它怎么知道要分配这个数据块,又应该如何分配,它不知道,它也不知道你所调用的函数是否是线程安全,所以说,我们在创建新线程时,一定不要调用操作系统的CreateThread(Windows API)函数,相反,我们始终应当调用CRT函数_beginthreadex,原型如下:

[cpp] view plaincopy
  1. unsigned long _beginthreadex
  2. (
  3. void *security,
  4. unsigned stack_size,
  5. unsigned (*start_address)(void *),
  6. void *arglist,
  7. unsigned initflag,
  8. unsigned *thrdaddr
  9. );

_beginthreadex与CreateThread的对数列表是一样的,只是参数名与类型不同,因为CRT函数不应该依赖于Windows的数据类型,下面有一个宏,来将CreateThread函数替换成_beginthreadex:

[cpp] view plaincopy
  1. typedef unsigned (__stdcall * PTHREAD_START) (void *);
  2. #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \
  3. pvParam, fdwCreate, pdwThreadID) \
  4. ((HANDLE) _beginthreadex( \
  5. (void *) (psa), \
  6. (unsigned) (cbStack), \
  7. (PTHREAD_START) (pfnStartAddr),\
  8. (void *) (pvParam),\
  9. (unsigned) (fdwCreate), \
  10. (unsigned *) (pdwThreadID)))

注意,_beginthreadex函数只存在于CRT库的多线程版本中,如果你的程序链接到一个CRT单线程版本中,那么程序在链接时就会报错,所以在用VS开发时,要注意这一点。

VS里面设置如下图所示:

Q:为什么说_beginthreadex就要比CreateThread更好,你是怎么知道的?

A:由于Microsoft 已经为CRT函数提供了源码,我们可以看到_beginthreadex到底比CreateThread多做了些什么事情,源码在Program Files\Microsoft Visual Studio 8\VC\crt\src\Threadex.c中,可以找到_beginthreadex的实现,这里是它的实现:

_beginthreadex的源码 
[cpp] view plaincopy
  1. _MCRTIMP uintptr_t __cdecl _beginthreadex (
  2. void *security,
  3. unsigned stacksize,
  4. unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
  5. void * argument,
  6. unsigned createflag,
  7. unsigned *thrdaddr
  8. )
  9. {
  10. _ptiddata ptd;
  11. uintptr_t thdl;
  12. unsigned long err = 0L;
  13. unsigned dummyid;
  14. _VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);
  15. __set_flsgetvalue();
  16. if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )
  17. goto error_return;
  18. _initptd(ptd, _getptd()->ptlocinfo);
  19. ptd->_initaddr = (void *) initialcode;
  20. ptd->_initarg = argument;
  21. ptd->_thandle = (uintptr_t)(-1);
  22. #if defined (_M_CEE) || defined (MRTDLL)
  23. if(!_getdomain(&(ptd->__initDomain)))
  24. {
  25. goto error_return;
  26. }
  27. #endif
  28. if ( thrdaddr == NULL )
  29. thrdaddr = &dummyid;
  30. if ( (thdl = (uintptr_t)
  31. CreateThread( (LPSECURITY_ATTRIBUTES)security,
  32. stacksize,
  33. _threadstartex,
  34. (LPVOID)ptd,
  35. createflag,
  36. (LPDWORD)thrdaddr))
  37. == (uintptr_t)0 )
  38. {
  39. err = GetLastError();
  40. goto error_return;
  41. }
  42. return(thdl);
  43. error_return:
  44. _free_crt(ptd);
  45. if ( err != 0L )
  46. _dosmaperr(err);
  47. return( (uintptr_t)0 );
  48. }

我们要明确几点:

1)每个线程都有自己的专用的_tiddata内存块,它是从C/C++的堆是分配出来的。

2)传给_beginthreadex的线程处理函数地址(线程的回调函数地址)是存在_tiddata内存块中的。

3)_beginthreadex内部的确调用了CreateThread来创建线程,这(CreateThread)是操作系统创建线程的唯一方式。

4)退出线程时调用_endthreadex,它内部调用了API ExitThread,它会释放创建线程在堆上分配的内存_tiddata。

Q:我要怎么终止线程?

A:与_beginthreadex相对应的退出线程的函数是_endthreadex,CreateThread 对应 ExitThread,一般情况下我们不要调用这两个函数来终止线程,最好是让线程走完它的线程处理函数,让它自生自灭。如果要调用的话,最好调用_endthreadex,但一般不推荐。

OK, 目前为止你应该对谁更好些的问题有了深入的了解,但是为什么调用CreateThread的程序仍然可以经年累月的正常运行呢?当线程调用一个需要 tiddata结构的CRT函数时(大多数CRT函数是线程安全的,并不需要该结构),首先CRT函数试图获取线程的数据块的地址(通过调用 TlsGetValue),然后,如果返回NULL,说明调用线程没有相关联的tiddata块,那么CRT函数马上为调用线程分配并初始化一个 tiddata块,并将该内存块关联到线程(通过TlsSetValue),这样,该CRT函数以及其他CRT函数都可以使用该线程的tiddata块了 (此即所谓"前人栽树后人乘凉"了,_)。

当然,如果说你的线程运行的时候一直没有问题是几乎不可能的。事实上,的确有一些问题需要说说。如 果线程使用了CRT的signal函数,整个进程都会被中止,因为结构化异常处理体尚未准备好。同样,如果不调用_endthreadex来中止线程就会 造成内存泄漏,如果使用_beginthreadex,当然会容易想到_endthreadex,但如果你习惯了使用CreateThread,是否还会 想起_endthreadex,我表示极大的怀疑,而且CreateThread/_endthreadex的组合怎么看怎么让人别扭。
不要忘记 开始的问题,接下来让我们再来看看效率问题。CRT库的多线程版本在某些函数里面放置了同步原语,比如malloc,为了保证堆不会被同时调用的 malloc函数破坏,这不可避免地会对效率造成影响,C/C++的哲学我们不应忘记,"决不为自己没有用到的付出代价",自然,我们无权要求单线程程序 为多线程程序付出它们不该付出的代价,所以,开头的问题也有了答案。
上面所说的都是静态链接的CRT库,而CRT库的动态链接版本则被编写得更加 通用,以便能够被任何运行的程序和DLL共享。正是基于这个原因,这个版本的库只存在多线程版本。因为CRT库是以DLL形式提供的,程序和DLL不需要 包含CRT库的任何代码,自然尺寸也就更小。同时,如果Microsoft修正了CRT库DLL中的Bug,程序也就自然受益了。

总结

首先,如果你调用_beginthreadex,你会获得线程的句柄,句柄当然需要关闭,但_endthreadex并没有这么做。通 常是调用_beginthreadex的线程(很可能是主线程)来调用CloseHandle关闭不再需要的新线程的句柄。其次,如果你使用CRT函数, 你只需要使用_beginthreadex即可。如果不使用,那么你可以只使用CreateThread。同样,如果只有一个线程(主线程)使用 CRT,你也可以使用CreateThread;如果新创建的线程不使用CRT,那么你也不需要_beginthreadex和多线程CRT。

_beginthreadex与CreateThread区别与联系相关推荐

  1. _beginthreadex和CreateThread的区别

    程序员对于Windows程序中应该用_beginthread还是CreateThread来创建线程,一直有所争论.本文将从对CRT源代码出发探讨这个问题. I. 起因 使用_beginthread还是 ...

  2. _beginthreadex、CreateThread、AfxBeginThread的选择

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

  3. 用_beginthreadex不用 CreateThread

    http://www.cnblogs.com/lcchuguo/p/5224576.html 在用visual studio进行界面编程时(如MFC),前台UI我们能够通过MFC的消息循环机制实现.而 ...

  4. CreateThread 和_beginthreadex区别

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

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

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

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

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

  7. 秒杀多线程第二篇 多线程第一次亲密接触 CreateThread与_beginthreadex本质区别

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

  8. CreateThread、_beginthreadex和AfxBeginThread 的区别

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

  9. CreateThread与_beginthreadex

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

最新文章

  1. java 获取nodejs端Gson数据案例(请求MongoDB)
  2. boost::safe_numerics模块实现检测到错误表达式评估的测试程序
  3. 从static变量导出问题解析 __declspec(dllexport) 和 __declspec(dllimport)的作用
  4. LeetCode 444. 序列重建(拓扑排序)
  5. python如何创建一个列表_在python中创建列表的最佳和/或最快方法
  6. html5程序自动登录,Jtro的技术分享:网页调起unity的exe程序并自动登录
  7. MySQL 面试必备 8 个知识点
  8. 有关 strongSwan 的英文文档
  9. Mac新手使用技巧——Mac音量调节
  10. vue 本地预览多图上传
  11. iOS学习——ViewController(六)
  12. 动态网页抓取数据软件
  13. c语言作业 查重,【学生研究课题】文档相似性检测系统(作业查重)
  14. 百度(杭州)创新中心人工智能产业论坛落幕,看AI如何引发行业革命
  15. 简单的创建app id 的流程
  16. 钉钉开发之使用HTTP请求获取你的公网出口IP
  17. 如何安装最新igraph 现在已经无坑了
  18. zxing 生成二维码,可设置logo、二维码颜色、白边大小
  19. 一个实用的在线文档格式转换器
  20. 内角均为120度六边形面积求解(xdoj 1118)

热门文章

  1. 基于注解的AOP实现事务控制及问题分析
  2. API Gateway简介
  3. SpringBoot_web开发-webjars静态资源映射规则
  4. Redisson框架快速入门
  5. hamcrest详细介绍
  6. Java平台扩展机制#3:SLF4J怪招
  7. 气体润滑轴承matlab分析,气体润滑轴承的研究与发展
  8. 创建用户赋权限_MySQL实战第84篇:MySQL数据库添加用户和赋权限,在8.0版本!
  9. 【报错笔记】在eclipse中复制代码后代码中会报画红线错,而且项目也会报错。
  10. 请问在allegro中如何在铜箔上单独放置过孔?