第六章线程的基础知识

6.3编写第一个线程函数

针对线程函数的几点说明:

  • 线程函数可以使用任何名字。实际上,如果在应用程序中拥有多个线程函数,必须为它们赋予不同的名字,否则编译器/链接程序会认为你为单个函数创建了多个实现函数
  • 可以给线程函数传递单个参数,参数的含义由你而不是由操作系统来定义
  • 线程函数必须返回一个值,它将成为该线程的退出代码。
  • 线程函数(实际上是你的所有函数)应该尽可能使用函数参数和局部变量。

6.4CreateThread函数

HANDLE CreateThread(
    PSECURITY_ATTRIBUTES psa,
    DWORD cbStack,
    PTHREAD_START_ROUTINE pfnStarAddr,
    PVOID pvParam,
    DWORD fdwCreate,
    PDWORD pdwThreadID );

当CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。

CreateThread的各个参数

  • psa
    psa参数是指向SECURITY_ATTRIBUTES结构的指针。如果想要该线程内核对象的默认安全属性,可以(并且通常能够)传递NULL。
typedef struct _SECURITY_ATTRIBUTES {
 DWORD  nLength;   //结构大小,可用sizeof取得
 LPVOID lpSecurityDescriptor; //指向一个对象的安全描述符 该安全描述符控制对象的共享 如果为NULL,则该对象使用调用进程的默认安全描述符 
 BOOL   bInheritHandle; //安全描述的对象能否被新创建的进程继承返回句柄 若为TRUE 则新进程继承该句柄
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

安全描述符相关介绍

  • cbStack
    cbStack参数用于设定线程可以将多少地址空间用于它自己的堆栈。每个线程拥有它自己
    的堆栈。
    可以使用链接程序的/STACK开关来控制这个值
    /STACK:[reserve][.commit]
    reserve参数用于设定系统应该为线程堆栈保留的地址空间量,默认值为1MB。
    commit参数用于设定开始时应该承诺用于堆栈保留区的物理存储器的容量,默认值1页。
  • pfnStartAddr和pvParam
    pfnStartAddr参数用于指明想要新线程执行的线程函数的地址.线程函数的PVParam参数与原先传递给CreateThread的PVParam参数是相同的。 CreateThread使用该参数不做别的事情,只是在线程启动执行时将该参数传递给线程函数。该参数提供了一个将初始化值传递给线程函数的手段。
    创建多个线程,使这些线程拥有与起始点相同的函数地址,这是完全合乎逻辑的并且是非常
    有用的。
  • fdwCreate
    fdwCreate参数可以设定用于控制创建线程的其他标志。它可以是两个值中的一个。如果
    该值是0,那么线程创建后可以立即进行调度。如果该值是CREATE_SUSPENDED,系统可以
    完整地创建线程并对它进行初始化,但是要暂停该线程的运行,这样它就无法进行调度。(CREATE_SUSPENDED)这个参数并不常用。
  • pdwThreadID
    它必须是DWORD的一个有效地址。

6.5终止线程的运行

一、终止线程的方法:

  • 线程函数返回【强烈推荐】
    如果线程能够返回,就可以确保如下事项:
    1)在线程函数中创建的所有C + +对象均将通过它们的撤消函数正确地撤消。
    2)操作系统将正确地释放线程堆栈使用的内存。
    3)系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。
    4)系统将递减线程内核对象的使用计数。
  • ExitThread函数【最好不要使用】
VOID EixtThread(DWORD dwExitCode);

该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。可以使用 EXitThread的得ExitThread参数告诉系统将线程的退出代码设置为什么。

  • TerminateThread函数【尽量避免使用】
BOOL TerminateThread(HANDLE hThread,
  DWORD dwExitCode);

ExitThread总是撤消调用的线程, 而TerminateThread能够撤消任何线程。
hThread参数用于标识被终止运行的线程的句柄当线程终止运行时,它的退出代码成为你作为dwExitCode参数传递的值。同时,线程的内核对象的使用计数也被递减。
需要注意的地方:
1)Terminate函数是一部运行的函数,即它告诉系统说你想要线程终止运行,但是,当函数返回时,不能保证线程被撤销。
2)当使用返回或调用ExitThread的方法撤消线程时,该线程的内存堆栈也被撤消。但是,如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈。

  • 在进程中终止运行时撤销线程【尽量避免使用】
    ExitProcess和TerminateProcess函数也可以用来终止线程的运行。这两个函数会导致进程中的
    剩余线程被强制撤消,就像从每个剩余的线程调用TerminateThread一样,这意味着正确的应用程序清楚没有发生,即C++对象撤销函数没有被调用,数据没有转至磁盘等等。

二、线程终止运行时发生的操作

  • 线程拥有的所有用户对象(窗口和挂钩)都被释放。
  • 线程的退出代码从STILL_ACTIVE改为传递给ExitThread或TerminateThread的代码。
  • 线程内核对象的状态变为已通知。
  • 如果线程是进程中的最后一个活动线程,系统也将进程视为已经终止运行
  • 线程内核对象的使用计数减1
    别的线程可以调用GetExitCodeThread来检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码。
BOOL GetExitCodeThread(
    HANDLE hThread,
    PDWORD pdwExitCode);

退出代码的值在pdwExitCode指向的DWORD中返回。如果调用GetExitCodeThread时线程
尚未终止运行,该函数就用 STILL_ACTIVE标识符(定义为 0x103)填入DWORD。如果该函
数运行成功,便返回TRUE。

6.6线程的一些性质


每个线程都有它自己的一组CPU寄存器,称为线程的上下文。该上下文反映了线程上次运
行时该线程的CPU寄存器的状态。CPU寄存器保存在一个CONTEXT结构中,CONTEXT结构本身则包含在线程的内核对象中。
下面是BaseThreadStart函数执行的基本操作:

VOID BaseThreadStart(PTHREAD_START_ROUTINE
 pfnStartAddr, PVOID pvParam)
{
  __try {
    ExitThread((pfnStartAddr)(pvParam));
  }
  __except(UnhandExceptionFilter(GetExceptionInformation())){
    ExitProcess(GetExceptionCode());
  }
}

由于新线程的指令指针被置为BaseThreadStart,因此该函数实际上是线程开始执行的地方。
当执行BaseThreadStart函数时,将出现如下情况:

  • 在线程函数中建立一个结构化异常处理(SEH)帧,这样,在线程执行时产生的任何异常情况都会得到系统的某种默认处理。
  • 系统调用线程函数,并将你传递给CreateThread函数的pvParam参数传递给它。
  • 当线程函数返回时, BaseThreadStart调用ExitThread并将线程函数的返回值传递给它。
    该线程内核对象的使用计数被递减,线程停止执行。
  • 如果线程产生一个没有处理的异常条件,由BaseThreadStart函数建立的SEH帧将负责处理
    该异常条件。通常情况下,这意味着向用户显示一个消息框,并且在用户撤消该消息框时,
    BzsethreadStart调用ExitThread,以终止整个进程的运行,而不只是终止线程的运行。

6.7 C/C++运行期库的考虑

若要创建一个新线程,绝对不要调用操作系统的CreateThread函数,必须调用C/C++运行期库函数_beginthreadex:

unsigned long _beginthreadex(
    void *securtiy,
    unsigned stack_size;
    unsigned (*start_adderss)(void*),
    void *arglist,
    unsigned initflag,
    unsigned *thrdaddr);

_beginthreadex函数的参数列表与CreateThread函数的参数列表是相同的,但是参数名和类型并不完全相同。
**_beginthreadex的一些要点:

  • 每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构
  • 传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。包括传递给该函数的参数
  • _beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。
  • 当调用CreateThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。传递给线程函数的参数是tiddata结构而不是pvParam的地址。
  • 成功返回句柄,失败返回NULL

关于_threadstartex的一些重点:

  • 新线程开始从BaseThreadStart函数执行,然后转移到_threadstartex。
  • 到达该新线程的tiddata块的地址作为其唯一参数被传递给_threadstartex
  • TlsSetValue是个操作系统函数,负责将一个值与调用线程联系起来。这称为线程本地存储器(TLS)
  • 一个SEH帧被放置在需要的线程函数周围
  • 调用必要的线程函数,传递必要的参数。【函数和参数的地址由_beginthreadex保存在tiddata块中】
  • 必要的线程函数返回值被认为是线程的退出代码。其是先调用C/C++运行期库函数_endthreadex,并传递退出代码。
    _endthreadex的一些要点
  • C运行期库的_getptd函数内部调用操作系统的TlsGetValue函数,该函数负责检索调用线程的tiddata内存块的地址。
  • 该数据块被释放,操作系统的ExitThread函数被调用,以便真正撤销该线程。

6.8 对自己的ID概念有所了解

HANDLE GetCurrentProcess();
HANDLE GetCurrentThread();
这个两个函数返回调用线程的进程的伪句柄或线程内核对象的伪句柄。
DWORD GetCurrentProcessId();
DWORD GetCurrentThreadId();
利用函数DuplicateHandle伪句柄转化为实句柄

BOOL DuplicateHandle(
  HANDLE hSourceProcess,
  HANDLE hSource,
  HANDLE hTargetProcess,
  PHANDLE  phTarget,
  DWORD fdwAcess,
  BOOL bInheritHandle,
  DWORD fdwOptions);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容