转载自:https://blog.csdn.net/lyzyung/article/details/38513563

https://blog.csdn.net/xuanyin235/article/details/77689512

一、背景介绍

★两套API :OS API vs CRT API

  首先,Windows操作系统本身提供了线程的创建函数CreateThread 和销毁函数ExitThread 。其中的CreateThread 用于创建线程,ExitThread 用于在线程函数内部推出线程(也就是自杀)。

  其次,在Visual C++自带的C运行库(以下简称CRT)中,还带了另外4个API函数,分别是:_beginthread ,_endthread ,_beginthreadex ,_endthreadex 。其中的_beginthread 和_beginthreadex 用于创建线程(它们内部调用了CreateThread ),_endthread 和_endthreadex 用于自杀(它们内部调用了ExitThread )。

  有同学看到这里,被搞懵了,心想:“干嘛要搞这么多玩意儿出来糊弄人?有CreateThread 和ExitThread 不就够了嘛!”其实你有所不知,此中大有奥妙啊。

  因为OS API作为操作系统本身提供的API函数,它被设计为语言无关的。它们不光可以被C++调用,还可以被其它诸如VB、Python、Delphi等开发语言来调用。所以它们不会(也不能够)帮你处理一些和具体编程语言相关的琐事。

  而CRT API虽然最终还是要调用OS API来完成核心的功能,但是CRT API在不知不觉中多帮我们干了一些虽琐碎但重要的工作。(如果同学们想窥探一下CRT API内部都干了些啥,可以拜读一下Win32编程的经典名着《Windows 核心编程》的6.7 章节,里面介绍得挺细致的)

  费了这么多口水,无非是要同学们牢记:以后在Windows平台下开发多线程程序,千万不要 直接使用这两个线程API(也就是CreateThread 和ExitThread ),否则后果自负 :-)

  另外,顺便补充一下。除了上述提到的CRT库。其它一些Windows平台的C++库也可能提供了线程的启动函数(比如MFC的AfxBeginThread),这些函数也对OS API进行了包装,所以用起来也是安全的。

★三种死法

  说完了两套API,开始来讨论一下线程的几种死法。线程和进程一样,也有三种死法。详见如下:

  1、自然死亡

  一般来说,每个线程都会对应某个函数(以下称为“线程函数”)。线程函数是线程运行的主体。所谓的“自然死亡”,就是通过return 语句结束线程函数的执行。

  2、自杀

  所谓的“自杀”,就是当前线程通过调用某API把自己 给停掉。前面已经说了OS API的坏话,同学们应该明白不能 再用它们。那我们能否使用CRT API来进行自杀呢?请看MSDN上的相关文档 。上面说了,如果使用_endthread 和_endthreadex ,将导致析构函数不被 调用。

  3、它杀

  所谓的“它杀”,很明显,就是其它线程通过调用某API把当前线程给强行 停掉。对于Windows平台来说,实现“它杀”比较简单,使用TernimateThread 就直接干掉了(它杀也是最野蛮的)。

  ★类对象的析构

  把类对象分为三种:局部非静态对象、局部静态对象、非局部对象。由于非局部对象是在main之前就创建、在进程死亡时析构,暂时与线程扯不上太大关系。剩下的两种局部对象,在宿主线程(所谓宿主线程,就是创建该局部对象的线程)死亡时会受到什么影响捏?请看如下的对照表:

  局部非静态对象 局部静态对象
自然死亡
自杀 不能
它杀 不能

  从上述结果可以看出,Windows上线程的死法还是以自然死亡为最安全,这点和进程的死法类似。所以同学们在Windows上开发时,要尽量避免自杀和它杀。

★关于主线程之死

  所谓“主线程”,就是进程启动时,操作系统为该进程默认创建的第一个线程。通俗地讲,可以把main 函数看成是主线程的线程函数。

  主线程之死是有讲究的。由于前面已经阐述了非自然死亡的坏处,所以我们只讨论主线程自然死亡这一种情况。当主线程自然死亡时(也就是用return 从main 返回时),会导致exit 函数被调用,exit 函数就会开始清除当前进程的各种资源,为进程的死亡作准备。这时候,如果还有其它活着的线程,也会被一起干掉(其效果类似于它杀)。

  为了防止出现上述情况,主线程一定要负责最终的善后工作。务必等到其它线程都死了,它才能死。

二、创建线程

函数原型及参数说明:

//线程的开始
unsigned long _beginthread(void(_cdecl *start_address)(void *), //声明为void (*start_address)(void *)形式unsigned stack_size, //是线程堆栈大小,一般默认为0void *arglist //向线程传递的参数,一般为结构体
);unsigned long _beginthreadex( //推荐使用void *security,   //安全属性,NULL表示默认安全性unsigned stack_size, //是线程堆栈大小,一般默认为0unsigned(_stdcall  *start_address)(void *),    //声明为unsigned(*start_address)(void *)形式void *argilist,  //向线程传递的参数,一般为结构体unsigned initflag, //新线程的初始状态,0表示立即执行,CREATE_SUSPEND表示创建后挂起。unsigned *thrdaddr //该变量存放线程标识符,它是CreateThread函数中的线程ID。
); //创建成功条件下的将线程句柄转化为unsigned long型返回,创建失败条件下返回0//线程的结束
//释放线程空间、释放线程TLS空间、调用ExiteThread结束线程。
void _endthread(void);  // retval:设定的线程结束码,与ExiteThread函数的参数功能一样,
//其实这个函数释放线程TLS空间,再调用ExiteThread函数,但没有释放线程空间。
void _endthreadex(unsigned retval);

两组函数都是用来创建和结束线程的。这两对函数的不同点如下:
(1)从形式上开,_beginthreadex()更像CreateThread()。_beginthreadex()比_beginthread()多3个参数:intiflag,security和threadaddr。
(2)两种创建方式的线程函数不同。_beginthreadex()的线程函数必须调用_stdcall调用方式,而且必须返回一个unsigned int型的退出码。
(3)_beginthreadex()在创建线程失败时返回0,而_beginthread()在创建线程失败时返回-1。这一点是在检查返回结果是必须注意的。
(4)如果是调用_beginthread()创建线程,并相应地调用_endthread()结束线程时,系统自动关闭线程句柄;而调用_beginthreadx()创建线程,并相应地调用_endthreadx()结束线程时,系统不能自动关闭线程句柄。因此调用_beginthreadx()创建线程还需程序员自己关闭线程句柄,以清除线程的地址空间。

代码示例:

#include <Windows.h>
#include <process.h>
#include <iostream>
using namespace std;typedef struct  _STRUCT_DATA_
{int id; //用于标识出票idint tickets;
}_DATA,*_pDATA;CRITICAL_SECTION g_cs;unsigned __stdcall Fun1Proc(LPVOID lpParam);
unsigned __stdcall Fun2Proc(LPVOID lpParam);void main()
{HANDLE hThread[2] = {NULL,NULL};unsigned threadid[2] = {0};_DATA stru_data;stru_data.id = 0;stru_data.tickets = 100;hThread[0] = (HANDLE)_beginthreadex(NULL,0,Fun1Proc,&stru_data,0,&threadid[0]);hThread[1] = (HANDLE)_beginthreadex(NULL,0,Fun2Proc,&stru_data,0,&threadid[1]);InitializeCriticalSection(&g_cs);Sleep(4000);LeaveCriticalSection(&g_cs);
}unsigned __stdcall Fun1Proc(LPVOID lpParam)
{_pDATA data = (_pDATA)lpParam;while(TRUE){EnterCriticalSection(&g_cs);if (data->tickets>0){Sleep(1);cout<<"id: "<<data->id++<<endl;cout<<"thread1 sell<<  ticket: "<<data->tickets--<<endl;LeaveCriticalSection(&g_cs);}else{LeaveCriticalSection(&g_cs);break;}}return 0;
}unsigned __stdcall Fun2Proc(LPVOID lpParam)
{_pDATA data = (_pDATA)lpParam;while(TRUE){EnterCriticalSection(&g_cs);if (data->tickets>0){Sleep(1);cout<<"id: "<<data->id++<<endl;cout<<"thread2 sell  ticket: "<<data->tickets--<<endl;LeaveCriticalSection(&g_cs);}else{LeaveCriticalSection(&g_cs);break;}}return 0;
}

C++多线程——_beginthread()和_beginthreadex相关推荐

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

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

  2. 多线程 CreateThread与_beginthreadex本质区别

    本文参考了 http://blog.csdn.net/morewindows/article/details/7421759 主函数创建一个线程,并且等待它执行完毕 #include <iost ...

  3. 创建线程——_beginthread 和 _beginthreadex【方法2】

    并不是Windows标准API,创建线程函数,该函底层调用CreateThread. 头文件 #include <process.h> 函数原型 unsigned long _begint ...

  4. 采用_beginthread/_beginthreadex函数创建多线程

    1.CRT简介: CRT: (C Runtime Library)即C运行时库,是系统运行的基础,包含了c常用的函数集(如:printf,malloc,strcpy等),为运行main做了初始化环境变 ...

  5. C++:多线程中的小白(1)基础概念

    一直在听别人说线程,进程,多线程等,觉得自己是知道的,其实上手后一滩烂泥啊..... 目录 一.概念(并发.可执行程序.进程) 二.C++11新标准线程库 一.概念(并发.可执行程序.进程) 1.并发 ...

  6. _beginthreadex 一定要自己写 CloseHandle 可以不用 _endthreadex

    _beginthreadex 一定要自己写 CloseHandle 可以不用 _endthreadex 天哪,好久不 写忘记了.一直记得线程的句柄只是 createthread 才需要关闭,可能是我用 ...

  7. 【读书笔记】【程序员的自我修养 -- 链接、装载与库(三)】函数调用与栈(this指针、返回值传递临时对象构建栈、运行库与多线程、_main函数、系统调用与中断向量表、Win32、可变参数、大小端

    文章目录 前言 介绍 内存 内存布局 栈与调用惯例 堆与内存管理 运行库 入口函数和程序初始化 C/C++运行库 运行库与多线程 C++全局构造与析构 fread 实现 系统调用与API 系统调用介绍 ...

  8. 网络程序设计——VC的多线程编程(线程与进程)

    目录 一.线程与进程 1.线程 2.引用线程的原因 3.线程与进程的关系 4.线程的特点 二.线程函数的参数传递 三.多线程与单线程的区别 四.线程创建函数_beginthread()和_begint ...

  9. CreateThread和_beginthread区别及使用http://blog.csdn.net/wxq1987525/article/details/6620210

    CreateThread和_beginthread区别及使用 标签: threadallocationsignalfunctionmicrosoftdll 2011-07-20 15:48 3968人 ...

  10. _beginthread和CreateThread 创建线程

    建立一个线程. unsigned long beginthread(void(cdecl *startaddress)(void*),unsigned stacksize, void *arglist ...

最新文章

  1. python基础 条件和循环
  2. Annovar注释的突变文件转MAF对象
  3. 解决xcode升级之后安装的插件失效
  4. Python安装教程分享
  5. mysql启动时执行sql server_常见 mysql 启动、运行.sql 文件错误处理
  6. linux下面实时查看进程,内存以及cpu使用情况使用命令
  7. Instant Run requires 'Tools | Android | Enable ADB integration' to be enabled.
  8. Flutter 本地小说阅读器
  9. 【Git分布式版本控制系统一】你还不会用Git进行项目管理?
  10. 数据挖掘实战—航空公司客户价值分析
  11. python名词_使用Python词性标记提取名词(循环)
  12. 宏转录组方法_土壤宏转录组RNA的提取方法评价
  13. 网络编程(2) - 网络通信方式-TCP
  14. Android获取音乐专辑封面图
  15. JavaScript随机漂浮碰壁效果
  16. 特岗教师计算机专业面试题,2019特岗教师面试试题及参考答案
  17. 网页部分第四次培训——JavaScript
  18. 疯狂Java讲义(十三)----第二部分
  19. 欧拉计划(project euler)最详细中文题解
  20. 计算机病毒不可侵入,计算机病毒考试题型.doc

热门文章

  1. 把“友商”装进芯里威联通运行黑群晖最新DSM系统
  2. C语言乘法口诀表代码
  3. 弹性卡箍零件自动化检测 与包装生产线的研制
  4. 如何解决存在的1px问题?
  5. ns账号切换服务器对存档有影响吗,switch主副账号切换与存档机制简介 - 步蜗网...
  6. C#/.NET 通过代码一键清理IE缓存文件/强制重置IE设置
  7. 网络配置实训(思科)
  8. 深度学习系列18:开源人脸识别库
  9. c语言图片输出,C语言输出图片?
  10. Java Spring Security 安全框架:(四)PasswordEncoder 密码解析器详解