`

多线程 CreateThread与_beginthreadex本质区别

 
阅读更多

本文参考了 http://blog.csdn.net/morewindows/article/details/7421759

主函数创建一个线程,并且等待它执行完毕

 

#include <iostream>
#include <windows.h>

using namespace std;

DWORD WINAPI threadFunc(PVOID pvParam) {
	cout << "hello " << (*((char *) pvParam))++ << "  " << GetCurrentThreadId() << endl;
	return 0;
}

int main() {

	char b[] = "abc";
	char *c = b;
	HANDLE handle = CreateThread(NULL, 0, threadFunc, (PVOID) &c, 0, NULL);
	WaitForSingleObject(handle, INFINITE);

	cout << c << endl;

	getchar();
	return 0;
}

 

下面来细讲下代码中的一些函数

第一个 CreateThread

函数功能:创建线程

函数原型:

HANDLEWINAPICreateThread(

  LPSECURITY_ATTRIBUTESlpThreadAttributes,

  SIZE_TdwStackSize,

  LPTHREAD_START_ROUTINElpStartAddress,

  LPVOIDlpParameter,

  DWORDdwCreationFlags,

  LPDWORDlpThreadId

);

函数说明:

第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。

第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

第四个参数是传给线程函数的参数。

第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。

第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

函数返回值:

成功返回新线程的句柄,失败返回NULL。 

 

第二个 WaitForSingleObject

函数功能:等待函数 – 使线程进入等待状态,直到指定的内核对象被触发。

函数原形:

DWORDWINAPIWaitForSingleObject(

  HANDLEhHandle,

  DWORDdwMilliseconds

);

函数说明:

第一个参数为要等待的内核对象。

第二个参数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待。

因为线程的句柄在线程运行时是未触发的,线程结束运行,句柄处于触发状态。所以可以用WaitForSingleObject()来等待一个线程结束运行。

函数返回值:

在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED

 

CreateThread()函数是Windows提供的API接口,在C/C++语言另有一个创建线程的函数_beginthreadex(),在很多书上(包括《Windows核心编程》)提到过尽量使用_beginthreadex()来代替使用CreateThread(),这是为什么了?下面就来探索与发现它们的区别吧。

 

首先要从标准C运行库与多线程的矛盾说起,标准C运行库在1970年被实现了,由于当时没任何一个操作系统提供对多线程的支持。因此编写标准C运行库的程序员根本没考虑多线程程序使用标准C运行库的情况。

 

我们以标准C运行库的全局变量errno为例。有的函数会在出错时,设置该变量。现在假定有这样的一个代码段:

bool fFailure = (system("NOTEPAD.EXE README.TXT") == -1);

if(fFailure) {
	switch(errno) {
		。。。
	}
}

 

假设在调用了system函数之后,并在执行if语句之前,执行上述代码的线程被中断了。另外还假设,这个线程被中断后,同一个进程中的另一个线程开始执行,而且这个新线程将执行另一个c运行库函数,后者设置了全局变量errno。当cpu后来被分配回第一个线程时,对于上述代码中的system函数调用,其errno反应的就不再是正确的错误码。

 

为了解决这个问题,每个线程都要有自己的errno变量,同时不能让他去修改另一个线程的errno变量。

这仅仅是证明了“标准c/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。

 

为了解决这个问题,Windows操作系统提供了这样的一种解决方案——每个线程都将拥有自己专用的一块内存区域来供标准C运行库中所有有需要的函数使用。而且这块内存区域的创建就是由C/C++运行库函数_beginthreadex()来负责的。下面列出_beginthreadex()函数的源代码(我在这份代码中增加了一些注释)以便读者更好的理解_beginthreadex()函数与CreateThread()函数的区别。

 

_MCRTIMP uintptr_t __cdecl _beginthreadex(
	void *security,
	unsigned stacksize,
	unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
	void * argument,
	unsigned createflag,
	unsigned *thrdaddr
)
{
	_ptiddata ptd;          //pointer to per-thread data 见注1
	uintptr_t thdl;         //thread handle 线程句柄
	unsigned long err = 0L; //Return from GetLastError()
	unsigned dummyid;    //dummy returned thread ID 线程ID号
	
	// validation section 检查initialcode是否为NULL
	_VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);

	//Initialize FlsGetValue function pointer
	__set_flsgetvalue();
	
	//Allocate and initialize a per-thread data structure for the to-be-created thread.
	//相当于new一个_tiddata结构,并赋给_ptiddata指针。
	if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )
		goto error_return;

	// Initialize the per-thread data
	//初始化线程的_tiddata块即CRT数据区域 见注2
	_initptd(ptd, _getptd()->ptlocinfo);
	
	//设置_tiddata结构中的其它数据,这样这块_tiddata块就与线程联系在一起了。
	ptd->_initaddr = (void *) initialcode; //线程函数地址
	ptd->_initarg = argument;              //传入的线程参数
	ptd->_thandle = (uintptr_t)(-1);
	
#if defined (_M_CEE) || defined (MRTDLL)
	if(!_getdomain(&(ptd->__initDomain))) //见注3
	{
		goto error_return;
	}
#endif  // defined (_M_CEE) || defined (MRTDLL)
	
	// Make sure non-NULL thrdaddr is passed to CreateThread
	if ( thrdaddr == NULL )//判断是否需要返回线程ID号
		thrdaddr = &dummyid;

	// Create the new thread using the parameters supplied by the caller.
	//_beginthreadex()最终还是会调用CreateThread()来向系统申请创建线程
	if ( (thdl = (uintptr_t)CreateThread(
					(LPSECURITY_ATTRIBUTES)security,
					stacksize,
					_threadstartex,
					(LPVOID)ptd,
					createflag,
					(LPDWORD)thrdaddr))
		== (uintptr_t)0 )
	{
		err = GetLastError();
		goto error_return;
	}

	//Good return
	return(thdl); //线程创建成功,返回新线程的句柄.
	
	//Error return
error_return:
	//Either ptd is NULL, or it points to the no-longer-necessary block
	//calloc-ed for the _tiddata struct which should now be freed up.
	//回收由_calloc_crt()申请的_tiddata块
	_free_crt(ptd);
	// Map the error, if necessary.
	// Note: this routine returns 0 for failure, just like the Win32
	// API CreateThread, but _beginthread() returns -1 for failure.
	//校正错误代号(可以调用GetLastError()得到错误代号)
	if ( err != 0L )
		_dosmaperr(err);
	return( (uintptr_t)0 ); //返回值为NULL的效句柄
}

 

讲解下部分代码:

1_ptiddataptd;中的_ptiddata是个结构体指针。在mtdll.h文件被定义:

      typedefstruct_tiddata * _ptiddata

微软对它的注释为Structure for each thread's data这是一个非常大的结构体,有很多成员。本文由于篇幅所限就不列出来了。

 

2_initptd(ptd_getptd()->ptlocinfo);微软对这一句代码中的getptd()的说明为:

      /* return address of per-thread CRT data */

      _ptiddata __cdecl_getptd(void);

_initptd()说明如下:

      /* initialize a per-thread CRT data block */

      void__cdecl_initptd(_Inout_ _ptiddata _Ptd,_In_opt_ pthreadlocinfo _Locale);

注释中的CRT C Runtime Library)即标准C运行库。

 

3if(!_getdomain(&(ptd->__initDomain)))中的_getdomain()函数代码可以在thread.c文件中找到,其主要功能是初始化COM环境。

 

由上面的源代码可知,_beginthreadex()函数最终还是会调用CreateThread()来向系统申请创建线程,_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。因此,如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()相信阅读到这里时,你会对这句简短的话有个非常深刻的印象,如果有面试官问起,你也可以流畅准确的回答了^_^

 

#include <windows.h>
#include <process.h>
#include <iostream>

using namespace std;

unsigned int __stdcall threadFunc(PVOID pM) {
	cout << * ((int *)pM) << endl;
	return 0;
}

int main() {
	int a = 9;

	HANDLE handle = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (PVOID) &a, 0, NULL);
	WaitForSingleObject(handle, INFINITE);

	getchar();
	return 0;
}

 

实例代码如上。本篇完结。

 

分享到:
评论

相关推荐

    _beginthreadex与CreateThread区别

    本文将深入探讨两种常见的线程创建函数:`CreateThread`和`_beginthreadex`,以及它们之间的本质区别。 `CreateThread`是Windows API提供的一个函数,允许程序员在进程中创建新的线程。它的函数原型如下: ```cpp ...

    深入分析多线程同步.doc

    总的来说,理解并掌握`CreateThread`和`_beginthreadex`的区别以及如何使用`WaitForSingleObject`进行线程同步,是编写高效、稳定的多线程程序的基础。在实际开发中,应根据项目需求和平台特性,合理选择线程创建...

    快速秒杀windows多线程

    4. CreateThread与_beginthreadex函数的区别:在多线程编程中,CreateThread和_beginthreadex都是用来创建线程的函数,但它们之间存在一些本质的区别。beginthreadex函数是C运行时库提供的函数,而CreateThread是...

    C/C++ 多线程的学习心得总结

    此外,虽然`_beginthreadex()`在Windows环境中广泛使用,但它的本质是封装了`CreateThread()`函数,后者是Windows API提供的直接创建线程的接口。`CreateThread()`的功能更为强大,可以直接控制线程的优先级、安全...

    基于三菱PLC和触摸屏的停车场智能管理系统设计与实现

    内容概要:本文详细介绍了基于三菱PLC和三菱触摸屏构建的停车场智能管理系统。系统分为入口、出口和管理中心三大部分,分别负责车辆身份识别、车位检测、道闸控制、缴费结算等功能。三菱PLC作为核心控制器,通过梯形图编程实现了车辆检测、道闸控制等关键逻辑;三菱触摸屏提供人机交互界面,支持参数设置、状态监控等功能。文中还讨论了PLC与触摸屏之间的通信配置,以及如何通过物联网技术将系统接入云端。 适合人群:从事智能交通系统开发的技术人员,尤其是熟悉三菱PLC编程和触摸屏应用的工程师。 使用场景及目标:适用于新建或改造停车场项目,旨在提高停车场管理效率和服务质量,减少人工干预,实现智能化运营。 其他说明:文中提供了具体的硬件配置建议、PLC编程实例、触摸屏界面设计指南及通信协议解析,有助于读者快速理解和实施类似项目。

    自动化生产领域:汇川AM系列PLC在全自动N95口罩机中的高级编程与控制应用

    内容概要:本文深入探讨了基于汇川AM401/AM403系列PLC和CODESYS高级编程模式构建的全自动N95口罩机控制系统。该系统涵盖了多个关键技术,包括轴控制(如绝对定位、相对定位)、凸轮同步控制、超声波焊接机控制、放卷张力控制、封边轴焊耳轴随动跟随控制、高速低速切换控制、步进电机精细控制等。此外,还介绍了IT7070系列触摸屏提供的友好交互界面及其产量统计功能。文章详细解析了各部分的具体实现方式,如通过ST语言编写复杂的控制逻辑,利用CAM_Profile生成器动态调整凸轮曲线,以及通过PID算法实现张力控制等。同时,强调了程序的模块化设计和详细的注释,便于维护和扩展。 适合人群:从事自动化生产设备开发的技术人员,尤其是熟悉PLC编程和CODESYS平台的工程师。 使用场景及目标:适用于希望深入了解全自动N95口罩机控制系统设计和实现的专业人士。主要目标是展示如何通过先进的编程技术和控制策略提升口罩生产的效率和质量。 其他说明:文中提到的实际案例和技术细节有助于读者更好地理解和应用相关技术,同时也为类似项目的开发提供了宝贵的参考资料。

    【嵌入式开发】Linux内核移植全流程解析:从准备工作到问题解决的详细指南

    内容概要:本文详细介绍了Linux内核移植在嵌入式开发中的重要性及其具体实施步骤。首先,强调了Linux内核移植作为连接硬件与软件桥梁的重要性,特别是在智能穿戴设备、工业自动化控制系统等广泛应用中的角色。文章随后解析了Linux内核移植的主要步骤,包括准备阶段(选择合适的内核版本、获取源码、配置交叉编译环境)、内核源码修改(硬件平台支持、时钟调整、机器码适配)、内核配置(通过make config、make menuconfig或make xconfig进行配置)、内核编译与安装。此外,还探讨了常见的移植问题及其解决方案,如串口打印异常、文件系统挂载故障和驱动适配难题。最后,通过一个具体的ARM架构开发板移植案例,展示了整个移植流程的实际操作,并展望了Linux内核移植技术的发展趋势。 适合人群:具备一定嵌入式开发基础,特别是对Linux内核有一定了解的研发人员和技术爱好者。 使用场景及目标:①帮助开发者理解Linux内核移植的基本概念和流程;②指导开发者在实际项目中进行Linux内核移植,解决常见问题;③为从事嵌入式系统开发的人员提供理论支持和技术参考。 其他说明:Linux内核移植是一项复杂但极具价值的任务,不仅需要扎实的理论知识,还需要丰富的实践经验。随着技术的进步,Linux内核移植技术也在不断发展,未来的方向将更加注重自动化和智能化,以提高移植效率和成功率。建议读者在学习过程中结合实际案例进行练习,逐步积累经验,掌握这一关键技术。

    识别多项式模型:项生成、结构检测、参数估计和动态验证

    实现全面的系统表征,包括候选项生成、结构检测、参数估计以及动态和静态模型验证。该软件包特别适用于分析具有固有噪声和误差的流动工厂系统,这些系统被建模为受白噪声破坏的二次多项式。 主要特点: 动态数据分析:处理输入和输出的时间序列数据,并验证数据集以进行识别和验证。 结构检测:删除不合适的聚类,并应用AIC和ERR等优化算法来细化模型结构。 参数估计:使用扩展最小二乘(ELS)或受限扩展最小二乘(RELS)计算模型参数。 模型验证:通过残差分析和相关系数评估模型性能。 静态模型仿真:生成静态响应并模拟各种输入条件下的系统行为。 方法概述: 该类包括支持识别过程的几种方法: generateCandidateTerms:构造一个用于系统特征描述的候选术语矩阵。 detectStructure:应用算法精确识别模型结构。 estimateParameters ELS:使用扩展最小二乘法估计动态模型参数。 estimateParameters RELS:使用受限扩展最小二乘法计算参数。 validateModel:分析模型准确性并验证残差行为。 buildStaticResponse:模拟静态模型对不同输入的响应。 displayModel:以文本和面板格式显示已识别的动态模型。 displayStaticModel:展示静态模型及其仿真结果。

    COMSOL变压器模型:时域与频域分析及磁致伸缩、噪声和洛伦兹力的多物理场仿真

    内容概要:本文详细介绍了如何使用 COMSOL Multiphysics 对变压器进行时域和频域分析,探讨了磁致伸缩、噪声和洛伦兹力的影响。文中通过具体的代码示例展示了如何设置时域和频域的边界条件,定义磁致伸缩系数,计算洛伦兹力,并通过多物理场耦合模拟变压器的振动和噪声。此外,还讨论了一些常见的仿真技巧和注意事项,如相位对齐、材料非线性特性和边界条件设置等。 适合人群:从事电力系统研究、变压器设计和仿真的工程师和技术人员。 使用场景及目标:适用于希望深入了解变压器内部物理机制及其对外界因素响应的专业人士。通过掌握这些方法,可以优化变压器设计,减少噪声,提升电力系统的稳定性和可靠性。 其他说明:文章不仅提供了理论背景,还给出了实用的代码片段和仿真技巧,帮助读者更好地理解和应用 COMSOL 进行变压器建模。

    linux系统~~~~~~~

    linux系统~~~~~~~~~~~~~

    TheIntroductionOfApache

    TheIntroductionOfApache(Apache的有关介绍)

    校园疫情防控管理平台 2025免费JAVA微信小程序毕设

    2025免费微信小程序毕业设计成品,包括源码+数据库+往届论文资料,附带启动教程和安装包。 启动教程:https://www.bilibili.com/video/BV1BfB2YYEnS 讲解视频:https://www.bilibili.com/video/BV1BVKMeZEYr 技术栈:Uniapp+Vue.js+SpringBoot+MySQL。 开发工具:Idea+VSCode+微信开发者工具。

    电气仿真中Matlab/Simulink的应用:电力电子、电机控制、新能源发电及电力系统的模型定制与优化

    内容概要:本文详细介绍了Matlab/Simulink在电气仿真领域的应用,涵盖多个方面。首先讨论了三相逆变器建模的关键参数设置,如载波频率和死区时间。接着探讨了电机控制中PI参数整定的方法,特别是永磁同步电机的矢量控制。对于新能源发电,着重讲解了光伏阵列的MPPT算法及其优化策略。此外,还涉及电力系统仿真的技巧,如自定义变压器模型和故障穿越功能的实现。文中提供了大量实用的代码片段,帮助读者更好地理解和应用这些技术。 适合人群:从事电力电子、电机控制、新能源发电以及电力系统仿真的工程师和技术人员。 使用场景及目标:①快速搭建和优化电力电子设备的仿真模型;②提高电机控制系统的设计效率和性能;③优化新能源发电系统的MPPT算法;④增强电力系统仿真的准确性和可靠性。 其他说明:文章强调了仿真过程中常见的问题及解决方案,提供了丰富的实战经验和技巧,有助于读者在实际工作中少走弯路。同时,鼓励读者利用Simulink自带的案例库进行学习和参考。

    MATLAB统计工具箱中的回归分析命令.pptx

    MATLAB统计工具箱中的回归分析命令.pptx

    NSAC全国重点标准化考试联盟认证试题计算机辅助设计AutoCAD.doc

    NSAC全国重点标准化考试联盟认证试题计算机辅助设计AutoCAD.doc

    精灵传信系统 精灵通讯技术 自定义对接易支付 支持网站+小程序双端源码.zip

    精灵传信支持在线提交发送短信,查看回复短信,在线购买额度,自定义对接易支付,设置违禁词,支持网站+小程序双端。 环境要求: PHP >= 73 MySQL>=5.6 Nginx>=1.6 系统安装教程 1.导入安装包里的数据库 2.打开.env文件填写数据库信息 3.设置运行目录public 4.设置伪静态thinkphp 后台账号密码分别是admin,123456

    自动化压测重启Android手机设备

    1. 插上手机后会自动检测手机是否连接,连接成功后会自动重启; 2. 电脑上有adb 环境; 3. 电脑上装有grep 程序

    Matlab-第七讲:编程基础II(-函数-).pptx

    Matlab-第七讲:编程基础II(-函数-).pptx

    基于遗传算法与免疫算法的物流配送中心选址优化及VRP路径规划(MATLAB实现)

    内容概要:本文详细介绍了利用遗传算法和免疫算法解决物流配送中心选址问题的方法,并提供了完整的MATLAB源码及注释。文章首先阐述了物流配送中心选址的重要性和挑战,然后重点讲解了适应度函数的设计,包括处理容量约束和超载惩罚。接着介绍了种群初始化、交叉操作、变异操作的具体实现细节,以及如何通过动态调整变异率来避免早熟收敛。此外,还探讨了免疫算法的应用,通过引入抗体浓度机制防止算法陷入局部最优。最后展示了算法的实际效果,包括运输成本的显著降低和车辆满载率的提升。文中提供的代码具有良好的扩展性,能够适应不同的物流网络规模和需求。 适合人群:从事物流管理、运筹优化领域的研究人员和技术人员,特别是对遗传算法、免疫算法感兴趣的开发者。 使用场景及目标:适用于需要优化物流配送中心选址的企业和个人。主要目标是通过合理的数学建模和智能算法,降低运输成本,提高运营效率,实现资源的最佳配置。 其他说明:本文不仅提供理论解释,还包括详细的代码实现和调优建议,帮助读者更好地理解和应用相关算法。同时,代码中预留了多种扩展接口,方便进一步研究和改进。

    S7-200 PLC实现六位密码锁系统的详细解析及应用场景

    内容概要:本文详细介绍了一套基于西门子S7-200 PLC的六位密码锁系统的设计与实现。首先介绍了系统的硬件配置,包括六个数字输入点、四个功能键以及三个状态指示灯。接着深入讲解了密码锁的关键代码,如输入检测、密码比对、错误处理和防破解机制。文中还分享了许多实际调试的经验和技术细节,如按键防抖、移位寄存器的应用、指针寻址和循环比较等。此外,作者还讨论了如何优化程序性能,提高系统的稳定性和安全性。 适合人群:具备一定PLC编程基础的技术人员,尤其是从事工业自动化领域的工程师。 使用场景及目标:适用于需要高安全性和可靠性的门禁控制系统,如工厂车间、仓库等场所的安全门管理。主要目标是通过PLC实现一个稳定的六位密码锁系统,防止未经授权的访问。 其他说明:文中提供了详细的代码示例和调试技巧,帮助读者更好地理解和实现该系统。同时,作者还提到未来可能加入指纹识别等高级功能,进一步提升系统的安全性。

Global site tag (gtag.js) - Google Analytics