转自:http://blog.csdn.net/zhouxuguang236/article/details/7775232

在一个牛人的博客上看到了这篇文章,所以就转过来了,地址是http://blog.csdn.net/morewindows/article/details/7421759

本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beginthreadex到底有什么区别,在实际的编程中到底应该使用CreateThread还是_beginthreadex?

使用多线程其实是非常容易的,下面这个程序的主线程会创建了一个子线程并等待其运行完毕,子线程就输出它的线程ID号然后输出一句经典名言——Hello World。整个程序的代码非常简短,只有区区几行。

[cpp] view plaincopy
  1. //最简单的创建多线程实例
  2. #include <stdio.h>
  3. #include <windows.h>
  4. //子线程函数
  5. DWORD WINAPI ThreadFun(LPVOID pM)
  6. {
  7. printf("子线程的线程ID号为:%d\n子线程输出Hello World\n", GetCurrentThreadId());
  8. return 0;
  9. }
  10. //主函数,所谓主函数其实就是主线程执行的函数。
  11. int main()
  12. {
  13. printf("     最简单的创建多线程实例\n");
  14. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  15. HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
  16. WaitForSingleObject(handle, INFINITE);
  17. return 0;
  18. }
[cpp] view plaincopy
  1. //最简单的创建多线程实例
  2. #include <stdio.h>
  3. #include <windows.h>
  4. //子线程函数
  5. DWORD WINAPI ThreadFun(LPVOID pM)
  6. {
  7. printf("子线程的线程ID号为:%d\n子线程输出Hello World\n", GetCurrentThreadId());
  8. return 0;
  9. }
  10. //主函数,所谓主函数其实就是主线程执行的函数。
  11. int main()
  12. {
  13. printf("     最简单的创建多线程实例\n");
  14. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  15. HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);
  16. WaitForSingleObject(handle, INFINITE);
  17. return 0;
  18. }

运行结果如下所示:

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

第一个 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。很多运行库中的函数在出错时会将错误代号赋值给这个全局变量,这样可以方便调试。但如果有这样的一个代码片段:

[cpp] view plaincopy
  1. if (system("notepad.exe readme.txt") == -1)
  2. {
  3. switch(errno)
  4. {
  5. ...//错误处理代码
  6. }
  7. }
[cpp] view plaincopy
  1. if (system("notepad.exe readme.txt") == -1)
  2. {
  3. switch(errno)
  4. {
  5. ...//错误处理代码
  6. }
  7. }

假设某个线程A在执行上面的代码,该线程在调用system()之后且尚未调用switch()语句时另外一个线程B启动了,这个线程B也调用了标准C运行库的函数,不幸的是这个函数执行出错了并将错误代号写入全局变量errno中。这样线程A一旦开始执行switch()语句时,它将访问一个被B线程改动了的errno。这种情况必须要加以避免!因为不单单是这一个变量会出问题,其它像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函数也会遇到这种由多个线程访问修改导致的数据覆盖问题。

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

[cpp] view plaincopy
  1. //_beginthreadex源码整理By MoreWindows( http://blog.csdn.net/MoreWindows )
  2. _MCRTIMP uintptr_t __cdecl _beginthreadex(
  3. void *security,
  4. unsigned stacksize,
  5. unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
  6. void * argument,
  7. unsigned createflag,
  8. unsigned *thrdaddr
  9. )
  10. {
  11. _ptiddata ptd;          //pointer to per-thread data 见注1
  12. uintptr_t thdl;         //thread handle 线程句柄
  13. unsigned long err = 0L; //Return from GetLastError()
  14. unsigned dummyid;    //dummy returned thread ID 线程ID号
  15. // validation section 检查initialcode是否为NULL
  16. _VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);
  17. //Initialize FlsGetValue function pointer
  18. __set_flsgetvalue();
  19. //Allocate and initialize a per-thread data structure for the to-be-created thread.
  20. //相当于new一个_tiddata结构,并赋给_ptiddata指针。
  21. if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )
  22. goto error_return;
  23. // Initialize the per-thread data
  24. //初始化线程的_tiddata块即CRT数据区域 见注2
  25. _initptd(ptd, _getptd()->ptlocinfo);
  26. //设置_tiddata结构中的其它数据,这样这块_tiddata块就与线程联系在一起了。
  27. ptd->_initaddr = (void *) initialcode; //线程函数地址
  28. ptd->_initarg = argument;              //传入的线程参数
  29. ptd->_thandle = (uintptr_t)(-1);
  30. #if defined (_M_CEE) || defined (MRTDLL)
  31. if(!_getdomain(&(ptd->__initDomain))) //见注3
  32. {
  33. goto error_return;
  34. }
  35. #endif  // defined (_M_CEE) || defined (MRTDLL)
  36. // Make sure non-NULL thrdaddr is passed to CreateThread
  37. if ( thrdaddr == NULL )//判断是否需要返回线程ID号
  38. thrdaddr = &dummyid;
  39. // Create the new thread using the parameters supplied by the caller.
  40. //_beginthreadex()最终还是会调用CreateThread()来向系统申请创建线程
  41. if ( (thdl = (uintptr_t)CreateThread(
  42. (LPSECURITY_ATTRIBUTES)security,
  43. stacksize,
  44. _threadstartex,
  45. (LPVOID)ptd,
  46. createflag,
  47. (LPDWORD)thrdaddr))
  48. == (uintptr_t)0 )
  49. {
  50. err = GetLastError();
  51. goto error_return;
  52. }
  53. //Good return
  54. return(thdl); //线程创建成功,返回新线程的句柄.
  55. //Error return
  56. error_return:
  57. //Either ptd is NULL, or it points to the no-longer-necessary block
  58. //calloc-ed for the _tiddata struct which should now be freed up.
  59. //回收由_calloc_crt()申请的_tiddata块
  60. _free_crt(ptd);
  61. // Map the error, if necessary.
  62. // Note: this routine returns 0 for failure, just like the Win32
  63. // API CreateThread, but _beginthread() returns -1 for failure.
  64. //校正错误代号(可以调用GetLastError()得到错误代号)
  65. if ( err != 0L )
  66. _dosmaperr(err);
  67. return( (uintptr_t)0 ); //返回值为NULL的效句柄
  68. }
[cpp] view plaincopy
  1. //_beginthreadex源码整理By MoreWindows( http://blog.csdn.net/MoreWindows )
  2. _MCRTIMP uintptr_t __cdecl _beginthreadex(
  3. void *security,
  4. unsigned stacksize,
  5. unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
  6. void * argument,
  7. unsigned createflag,
  8. unsigned *thrdaddr
  9. )
  10. {
  11. _ptiddata ptd;          //pointer to per-thread data 见注1
  12. uintptr_t thdl;         //thread handle 线程句柄
  13. unsigned long err = 0L; //Return from GetLastError()
  14. unsigned dummyid;    //dummy returned thread ID 线程ID号
  15. // validation section 检查initialcode是否为NULL
  16. _VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);
  17. //Initialize FlsGetValue function pointer
  18. __set_flsgetvalue();
  19. //Allocate and initialize a per-thread data structure for the to-be-created thread.
  20. //相当于new一个_tiddata结构,并赋给_ptiddata指针。
  21. if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )
  22. goto error_return;
  23. // Initialize the per-thread data
  24. //初始化线程的_tiddata块即CRT数据区域 见注2
  25. _initptd(ptd, _getptd()->ptlocinfo);
  26. //设置_tiddata结构中的其它数据,这样这块_tiddata块就与线程联系在一起了。
  27. ptd->_initaddr = (void *) initialcode; //线程函数地址
  28. ptd->_initarg = argument;              //传入的线程参数
  29. ptd->_thandle = (uintptr_t)(-1);
  30. #if defined (_M_CEE) || defined (MRTDLL)
  31. if(!_getdomain(&(ptd->__initDomain))) //见注3
  32. {
  33. goto error_return;
  34. }
  35. #endif  // defined (_M_CEE) || defined (MRTDLL)
  36. // Make sure non-NULL thrdaddr is passed to CreateThread
  37. if ( thrdaddr == NULL )//判断是否需要返回线程ID号
  38. thrdaddr = &dummyid;
  39. // Create the new thread using the parameters supplied by the caller.
  40. //_beginthreadex()最终还是会调用CreateThread()来向系统申请创建线程
  41. if ( (thdl = (uintptr_t)CreateThread(
  42. (LPSECURITY_ATTRIBUTES)security,
  43. stacksize,
  44. _threadstartex,
  45. (LPVOID)ptd,
  46. createflag,
  47. (LPDWORD)thrdaddr))
  48. == (uintptr_t)0 )
  49. {
  50. err = GetLastError();
  51. goto error_return;
  52. }
  53. //Good return
  54. return(thdl); //线程创建成功,返回新线程的句柄.
  55. //Error return
  56. error_return:
  57. //Either ptd is NULL, or it points to the no-longer-necessary block
  58. //calloc-ed for the _tiddata struct which should now be freed up.
  59. //回收由_calloc_crt()申请的_tiddata块
  60. _free_crt(ptd);
  61. // Map the error, if necessary.
  62. // Note: this routine returns 0 for failure, just like the Win32
  63. // API CreateThread, but _beginthread() returns -1 for failure.
  64. //校正错误代号(可以调用GetLastError()得到错误代号)
  65. if ( err != 0L )
  66. _dosmaperr(err);
  67. return( (uintptr_t)0 ); //返回值为NULL的效句柄
  68. }

讲解下部分代码:

注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()来创建多个子线程:

[cpp] view plaincopy
  1. //创建多子个线程实例
  2. #include <stdio.h>
  3. #include <process.h>
  4. #include <windows.h>
  5. //子线程函数
  6. unsigned int __stdcall ThreadFun(PVOID pM)
  7. {
  8. printf("线程ID号为%4d的子线程说:Hello World\n", GetCurrentThreadId());
  9. return 0;
  10. }
  11. //主函数,所谓主函数其实就是主线程执行的函数。
  12. int main()
  13. {
  14. printf("     创建多个子线程实例 \n");
  15. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  16. const int THREAD_NUM = 5;
  17. HANDLE handle[THREAD_NUM];
  18. for (int i = 0; i < THREAD_NUM; i++)
  19. handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
  20. WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  21. return 0;
  22. }
[cpp] view plaincopy
  1. //创建多子个线程实例
  2. #include <stdio.h>
  3. #include <process.h>
  4. #include <windows.h>
  5. //子线程函数
  6. unsigned int __stdcall ThreadFun(PVOID pM)
  7. {
  8. printf("线程ID号为%4d的子线程说:Hello World\n", GetCurrentThreadId());
  9. return 0;
  10. }
  11. //主函数,所谓主函数其实就是主线程执行的函数。
  12. int main()
  13. {
  14. printf("     创建多个子线程实例 \n");
  15. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  16. const int THREAD_NUM = 5;
  17. HANDLE handle[THREAD_NUM];
  18. for (int i = 0; i < THREAD_NUM; i++)
  19. handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
  20. WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  21. return 0;
  22. }

运行结果如下:

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

[cpp] view plaincopy
  1. //子线程报数
  2. #include <stdio.h>
  3. #include <process.h>
  4. #include <windows.h>
  5. int g_nCount;
  6. //子线程函数
  7. unsigned int __stdcall ThreadFun(PVOID pM)
  8. {
  9. g_nCount++;
  10. printf("线程ID号为%4d的子线程报数%d\n", GetCurrentThreadId(), g_nCount);
  11. return 0;
  12. }
  13. //主函数,所谓主函数其实就是主线程执行的函数。
  14. int main()
  15. {
  16. printf("     子线程报数 \n");
  17. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  18. const int THREAD_NUM = 10;
  19. HANDLE handle[THREAD_NUM];
  20. g_nCount = 0;
  21. for (int i = 0; i < THREAD_NUM; i++)
  22. handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
  23. WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  24. return 0;
  25. }
[cpp] view plaincopy
  1. //子线程报数
  2. #include <stdio.h>
  3. #include <process.h>
  4. #include <windows.h>
  5. int g_nCount;
  6. //子线程函数
  7. unsigned int __stdcall ThreadFun(PVOID pM)
  8. {
  9. g_nCount++;
  10. printf("线程ID号为%4d的子线程报数%d\n", GetCurrentThreadId(), g_nCount);
  11. return 0;
  12. }
  13. //主函数,所谓主函数其实就是主线程执行的函数。
  14. int main()
  15. {
  16. printf("     子线程报数 \n");
  17. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  18. const int THREAD_NUM = 10;
  19. HANDLE handle[THREAD_NUM];
  20. g_nCount = 0;
  21. for (int i = 0; i < THREAD_NUM; i++)
  22. handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
  23. WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
  24. return 0;
  25. }

对一次运行结果截图如下:

显示结果从1数到10,看起来好象没有问题。

答案是不对的,虽然这种做法在逻辑上是正确的,但在多线程环境下这样做是会产生严重的问题,下一篇《秒杀多线程第三篇 原子操作 Interlocked系列函数》将为你演示错误的结果(可能非常出人意料)并解释产生这个结果的详细原因。

转载于:https://www.cnblogs.com/x_wukong/p/5148143.html

windows多线程详解相关推荐

  1. Java 多线程详解(二)------如何创建进程和线程

    Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 在上一篇博客中,我们已经 ...

  2. iOS多线程详解:实践篇

    iOS多线程实践中,常用的就是子线程执行耗时操作,然后回到主线程刷新UI.在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程.由于在iOS中除了主线程,其他子线程是独立 ...

  3. python可以开多少线程_python多线程详解

    python多线程详解 一.线程介绍 什么是线程 线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位.线程自己不拥有系统资源,只拥有一 ...

  4. java多线程教程_java 基础教程之多线程详解及简单实例

    java 多线程详解 在这篇文章里,我们关注多线程.多线程是一个复杂的话题,包含了很多内容,这篇文章主要关注线程的基本属性.如何创建线程.线程的状态切换以及线程通信. 线程是操作系统运行的基本单位,它 ...

  5. Java多线程详解(线程不安全案例)

    嗨喽-小伙伴们我又来了, 通过前面两章的学习,我们了解了线程的基本概念和创建线程的四种方式. 附上链接: 1.  Java多线程详解(基本概念)​​​​​​​ 2. Java多线程详解(如何创建线程) ...

  6. Java多线程详解(基本概念)

    嗨喽-小伙伴们我来啦, 从本章开始,我们就要开始介绍Java中一个非常重要的概念-----多线程.线程化思想是计算机领域的重要思想,有了线程,咱编写的程序才能更为高效准确地运行起来. 首先,咱来了解一 ...

  7. Java 多线程详解(五)------线程的声明周期

    Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 Java 多线程详解(二 ...

  8. Java 多线程详解(四)------生产者和消费者

    Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 Java 多线程详解(二 ...

  9. Java 多线程详解(三)------线程的同步

    Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 Java 多线程详解(二 ...

最新文章

  1. nodejs模块加载分析(1).md
  2. ThinkPHP框架学习之CRUD
  3. 如何上传本地文件到github又如何删除自己的github仓库
  4. python:软件目录结构规范
  5. 运行第一个vue.js文件
  6. jQuery 树插件ZTree使用Demo
  7. Java项目--俄罗斯方块
  8. ubuntu桌面美化mac_Ubuntu16.04 主题美化
  9. Js事件对象EventUtil
  10. linux目录存 xml文件,将IDEA maven项目中src源代码下的xml等资源文件编译进classes文件夹...
  11. 基于ftp4j的FTP客户端工具
  12. MATLAB五子棋游戏(双人对战,可悔棋)
  13. proteus仿真串口
  14. linux bios格式化磁盘,BIOS设置与磁盘分区、格式化.doc
  15. 触目惊心的互联网流量劫持
  16. Dockerfile MAINTAINER和LABEL指令 语法解析
  17. 基于Matlab的空中交通管制仿真(附源码)
  18. 麒麟操作系统iso文件中的img文件的解压与压缩
  19. 论文中world自带公式编辑器编辑的公式每次打开公式自动加粗问题解决。
  20. Caffeine Cache和Ehcache3.x 进程缓存性能比较

热门文章

  1. 使用git建立本地仓储管理代码【转】
  2. Python 父目录获取
  3. [NHibernate]一对多关系(级联删除,级联添加)
  4. RMAN备份学习笔记
  5. 数据层(DAL)自动生成库
  6. java ant 详解
  7. Android_Bitmap_图片的二次采样并生成缩略图
  8. 04 Ribbon介绍
  9. nginx配置二级目录,反向代理不同ip+端口
  10. 如何利用华为交换机配置SSH登录