_beginthreadex与CreateThread区别与联系
关于这两个函数的区别,可以参考《Windows 核心编程(第五版)》的第六章 "线程基础",这篇文章的思想多数来源于此,我只是作了一些整理。
线程对于初学者还说可能觉得很高深,这可以理解。对于某些有经验的程序员来说,可能觉得又太简单,我觉得如果认为线程很简单的人,都是没有理解线程,线程里面涉及的东西太多,包括内存,初始化,线程同步等。我打算以QA的形式来写这篇文章。
Q:为什么书上说要以_beginthreadex来替代CreateThread?
A:好了,一直用API CreateThread来创建线程的同志们要注意了,你可能会说我一直用这个API来创建线程,工作刚刚的,一点问题都没有。如果真是这样的话,我只能说是你运气太好了。在_beginthreadex的内部,它调用了CreateThread来创建线程,Windows始终用CreateThread来创建线程。在调用CreateThread之前,beginthreadex它做了很多初始化的工作,所以它比CreateThread创建的线程更加安全。
Q:为什么要用两个相同功能的函数来对待单线程和多线程序程序呢?
A:这里就有一定的历史原因了,标准C语言的库在是1970年左右发明的,而在那时候,线程的概念尚未出现在任何一个操作系统上。但是,线程毕竟是出现了,让我们来看看下面这个例子,来说明以前的CRT为什么不支持多线程:
- BOOL fFailure = (system("NOTEPAD.EXE README.TXT") == -1);
- if (fFailure)
- {
- switch (errno)
- {
- case E2BIG:
- // Argument list or environment too big
- break;
- case ENOENT:
- // Command interpreter cannot be found
- break;
- case ENOEXEC:
- // Command interpreter has bad format
- break;
- case ENOMEM:
- // Insufficient memory to run command
- break;
- }
- }
假如代码是这种情况,当执行到"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,原型如下:
- unsigned long _beginthreadex
- (
- void *security,
- unsigned stack_size,
- unsigned (*start_address)(void *),
- void *arglist,
- unsigned initflag,
- unsigned *thrdaddr
- );
_beginthreadex与CreateThread的对数列表是一样的,只是参数名与类型不同,因为CRT函数不应该依赖于Windows的数据类型,下面有一个宏,来将CreateThread函数替换成_beginthreadex:
- typedef unsigned (__stdcall * PTHREAD_START) (void *);
- #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \
- pvParam, fdwCreate, pdwThreadID) \
- ((HANDLE) _beginthreadex( \
- (void *) (psa), \
- (unsigned) (cbStack), \
- (PTHREAD_START) (pfnStartAddr),\
- (void *) (pvParam),\
- (unsigned) (fdwCreate), \
- (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的源码
- _MCRTIMP uintptr_t __cdecl _beginthreadex (
- void *security,
- unsigned stacksize,
- unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
- void * argument,
- unsigned createflag,
- unsigned *thrdaddr
- )
- {
- _ptiddata ptd;
- uintptr_t thdl;
- unsigned long err = 0L;
- unsigned dummyid;
- _VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);
- __set_flsgetvalue();
- if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )
- goto error_return;
- _initptd(ptd, _getptd()->ptlocinfo);
- ptd->_initaddr = (void *) initialcode;
- ptd->_initarg = argument;
- ptd->_thandle = (uintptr_t)(-1);
- #if defined (_M_CEE) || defined (MRTDLL)
- if(!_getdomain(&(ptd->__initDomain)))
- {
- goto error_return;
- }
- #endif
- if ( thrdaddr == NULL )
- thrdaddr = &dummyid;
- if ( (thdl = (uintptr_t)
- CreateThread( (LPSECURITY_ATTRIBUTES)security,
- stacksize,
- _threadstartex,
- (LPVOID)ptd,
- createflag,
- (LPDWORD)thrdaddr))
- == (uintptr_t)0 )
- {
- err = GetLastError();
- goto error_return;
- }
- return(thdl);
- error_return:
- _free_crt(ptd);
- if ( err != 0L )
- _dosmaperr(err);
- return( (uintptr_t)0 );
- }
我们要明确几点:
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区别与联系相关推荐
- _beginthreadex和CreateThread的区别
程序员对于Windows程序中应该用_beginthread还是CreateThread来创建线程,一直有所争论.本文将从对CRT源代码出发探讨这个问题. I. 起因 使用_beginthread还是 ...
- _beginthreadex、CreateThread、AfxBeginThread的选择
_beginthreadex.CreateThread.AfxBeginThread的选择 收藏 1. Create/EndThread是Win32方法开始/结束一个线程 _beginthrea ...
- 用_beginthreadex不用 CreateThread
http://www.cnblogs.com/lcchuguo/p/5224576.html 在用visual studio进行界面编程时(如MFC),前台UI我们能够通过MFC的消息循环机制实现.而 ...
- CreateThread 和_beginthreadex区别
本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...
- 多线程之 CreateThread与_beginthreadex本质区别
本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...
- 【转】windows多线程CreateThread与_beginthreadex本质区别
本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...
- 秒杀多线程第二篇 多线程第一次亲密接触 CreateThread与_beginthreadex本质区别
本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...
- CreateThread、_beginthreadex和AfxBeginThread 的区别
CreateThread._beginthreadex和AfxBeginThread 创建线程好几个函数可以使用,可是它们有什么区别,适用于什么情况呢? 参考了一些资料,写得都挺好的,这里做一些摘抄和 ...
- CreateThread与_beginthreadex
本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beg ...
最新文章
- java 获取nodejs端Gson数据案例(请求MongoDB)
- boost::safe_numerics模块实现检测到错误表达式评估的测试程序
- 从static变量导出问题解析 __declspec(dllexport) 和 __declspec(dllimport)的作用
- LeetCode 444. 序列重建(拓扑排序)
- python如何创建一个列表_在python中创建列表的最佳和/或最快方法
- html5程序自动登录,Jtro的技术分享:网页调起unity的exe程序并自动登录
- MySQL 面试必备 8 个知识点
- 有关 strongSwan 的英文文档
- Mac新手使用技巧——Mac音量调节
- vue 本地预览多图上传
- iOS学习——ViewController(六)
- 动态网页抓取数据软件
- c语言作业 查重,【学生研究课题】文档相似性检测系统(作业查重)
- 百度(杭州)创新中心人工智能产业论坛落幕,看AI如何引发行业革命
- 简单的创建app id 的流程
- 钉钉开发之使用HTTP请求获取你的公网出口IP
- 如何安装最新igraph 现在已经无坑了
- zxing 生成二维码,可设置logo、二维码颜色、白边大小
- 一个实用的在线文档格式转换器
- 内角均为120度六边形面积求解(xdoj 1118)