本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beginthreadex到底有什么区别,在实际的编程中到底应该使用CreateThread还是_beginthreadex?
使用多线程其实是非常容易的,下面这个程序的主线程会创建了一个子线程并等待其运行完毕,子线程就输出它的线程ID号然后输出一句经典名言——Hello World。整个程序的代码非常简短,只有区区几行。
运行结果如下所示:

下面来细讲下代码中的一些函数
第一个 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。很多运行库中的函数在出错时会将错误代号赋值给这个全局变量,这样可以方便调试。但如果有这样的一个代码片段:
假设某个线程A在执行上面的代码,该线程在调用system()之后且尚未调用switch()语句时另外一个线程B启动了,这个线程B也调用了标准C运行库的函数,不幸的是这个函数执行出错了并将错误代号写入全局变量errno中。这样线程A一旦开始执行switch()语句时,它将访问一个被B线程改动了的errno。这种情况必须要加以避免!因为不单单是这一个变量会出问题,其它像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函数也会遇到这种由多个线程访问修改导致的数据覆盖问题。
为了解决这个问题,Windows操作系统提供了这样的一种解决方案——每个线程都将拥有自己专用的一块内存区域来供标准C运行库中所有有需要的函数使用。而且这块内存区域的创建就是由C/C++运行库函数_beginthreadex()来负责的。下面列出_beginthreadex()函数的源代码(我在这份代码中增加了一些注释)以便读者更好的理解_beginthreadex()函数与CreateThread()函数的区别。
讲解下部分代码:
注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运行库。
注3.if(!_getdomain(&(ptd->__initDomain)))中的_getdomain()函数代码可以在thread.c文件中找到,其主要功能是初始化COM环境。
由上面的源代码可知,_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。因此,如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()。相信阅读到这里时,你会对这句简短的话有个非常深刻的印象,如果有面试官问起,你也可以流畅准确的回答了^_^。
接下来,类似于上面的程序用CreateThread()创建输出“Hello World”的子线程,下面使用_beginthreadex()来创建多个子线程:
运行结果如下:

图中每个子线程说的都是同一句话,不太好看。能不能来一个线程报数功能,即第一个子线程输出1,第二个子线程输出2,第三个子线程输出3,……。要实现这个功能似乎非常简单——每个子线程对一个全局变量进行递增并输出就可以了。代码如下:
对一次运行结果截图如下:

显示结果从1数到10,看起来好象没有问题。
答案是不对的,虽然这种做法在逻辑上是正确的,但在多线程环境下这样做是会产生严重的问题,下一篇《秒杀多线程第三篇 原子操作 Interlocked系列函数》将为你演示错误的结果(可能非常出人意料)并解释产生这个结果的详细原因。
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7421759
如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。
分享到:
相关推荐
本文将深入探讨两种常见的线程创建函数:`CreateThread`和`_beginthreadex`,以及它们之间的本质区别。 `CreateThread`是Windows API提供的一个函数,允许程序员在进程中创建新的线程。它的函数原型如下: ```cpp ...
4. CreateThread与_beginthreadex函数的区别:在多线程编程中,CreateThread和_beginthreadex都是用来创建线程的函数,但它们之间存在一些本质的区别。beginthreadex函数是C运行时库提供的函数,而CreateThread是...
标题与描述概述的知识点主要集中在两个关键的线程创建函数——`_beginthread`与`CreateThread`的区别上。这两个函数都是在Windows编程环境中用于创建线程的重要手段,但它们在内部实现机制、资源管理以及与C运行时库...
第二种是启动多线程,不同模式下启动函数不同,mfc与API与WIN32下面注意点也是有区别的! VC启动一个新线程的三种方法,有需要的朋友可以参考下。 第一种AfxBeginThread() 用AfxBeginThread()函数来创建一个新...
在具体编程中,多线程的实现和应用需要依赖于操作系统提供的API或编程语言的库函数,例如在Windows平台上,CreateThread和_beginthreadex是两种常见的创建线程的API。前者是较为简单的创建线程的方式,而后者提供了...
《Windows多线程编程技术与实例(C++)》是一本深入探讨Windows环境下多线程编程的书籍,特别适合正在学习或已经从事C++多线程开发的人员阅读。本书通过丰富的实例,详细讲解了如何在Windows操作系统中利用C++进行...
#### 一、多线程编程的必要性与问题引入 在软件开发中,特别是在需要处理大量计算或等待I/O操作的场景下,单线程程序往往不能充分利用现代多核处理器的潜力,且容易出现用户界面卡顿的情况。例如,在Visual C++中,...
在VB6.0环境下,开发多线程应用程序是一项挑战,因为VB6本身并不直接支持多线程编程。然而,通过一些技巧和外部库,开发者可以实现这一目标。这个"堪称精品的VB多线程控制台源程序代码.rar"提供了一个示例,展示了...
本文档对C++多线程编程进行了总结,介绍了三种创建线程的方法:CreateThread函数、AfxBeginThread函数和_beginthread()/_beginthreadex()函数,同时对线程的管理和终止进行了详细的讲解。 CreateThread函数 ...
总的来说,理解并掌握`CreateThread`和`_beginthreadex`的区别以及如何使用`WaitForSingleObject`进行线程同步,是编写高效、稳定的多线程程序的基础。在实际开发中,应根据项目需求和平台特性,合理选择线程创建...
多线程编程的核心是理解和使用线程API,如CreateThread或_beginthreadex函数来创建新线程。在VC++中,MFC库提供了CWinThread类,它封装了Windows线程API,使得线程创建和管理更加简单。在描述中的"实例73-多线程访问...
在VC++编程环境中,多线程通信是一个关键的议题,特别是在开发高性能、高并发的应用程序时。多线程技术能够让我们充分利用多核处理器的优势,同时处理多个任务,提高程序的执行效率。本篇将深入探讨VC++中实现多线程...
在IT领域,尤其是在软件开发中,多线程技术是一种常用的方法,用于提高程序的执行效率和并发性。本文将深入探讨如何在VC++环境中利用大漠插件进行多线程编程,以实现模板化的高效解决方案。 首先,让我们理解什么是...
本篇文章将详细阐述VC++中实现多线程的三种常见方法:使用`CreateThread`函数、`_beginthread`/`_beginthreadex`函数以及基于`std::thread`的C++11标准库方法。 1. **使用CreateThread函数** Windows API提供的`...
在VC++编程环境中,多线程管理是一项关键的技术,它允许程序同时执行多个任务,从而提高应用程序的效率和响应性。"VC++多线程管理器_authornop_Vc_"项目提供了一个示例,用于演示如何在VC++中有效地创建、管理和同步...
标题 "一个多线程下载程序" 描述了一个使用VC++编程语言实现的软件,该软件能够通过多线程技术加速文件的下载过程。多线程下载是现代互联网应用程序中常见的优化技术,尤其对于大文件下载,它能显著提高下载速度,...
这个名为"一个多线程访问数据库的代码"的资源,显然是一个使用VC6.0编译器实现的C++项目,它展示了如何在Windows环境下通过多线程技术来并发地与数据库进行交互。下面我们将深入探讨这两个主题。 首先,多线程是一...
在IT行业中,多线程编程是一项至关重要的技术,特别是在开发高效、响应迅速的应用程序时。本文将深入探讨“vc多线程编程实例”,并详细解释如何利用多线程技术提高程序性能。 首先,多线程是指在一个应用程序中同时...
在IT领域,多线程编程是一项核心技术,尤其在开发高性能应用程序时显得尤为重要。本文将以"VC++介绍一个多线程下载程序"为主题,深入探讨多线程技术及其在VC++环境中的应用。 首先,多线程是指在一个进程中创建多个...