Chapter06-Thread讲解

来源:互联网 发布:经典的c语言书籍推荐 编辑:程序博客网 时间:2024/06/10 09:14

   与Process进程类似,每个Thread线程也是由两部分组成

  • 一个内核对象(Kernel Object);操作系统用之来管理线程,同时内核对象中还记录了该线程的各种统计信息。
  • 一个线程堆栈(Thread Stack);该堆栈用来记录函数参数和运行过程中的各变量。

何时创建线程:

     线程(Thread)描述了进程内部的执行路线;每次进程(Process)初始化时,都会执行基线程(Primary Thread)。对于我们平常编写的Win32程序,main()/_tWinMain()就是进程的基线程入口点了。然而基线程只是进程执行所必需的,为了顺利且高效得执行,进程有时就要创建其他线程以辅助其工作。

     一个典型的多线程进程就是office word软件。该进程需要完成分页,语法检查,后台打印等功能,软件运行时需要一个线程处理分页,一个线程进行语法检查,一个线程处理打印操作。


何时不适合创建线程:

毫无疑问,多线程可以提高效率等需要优点;但同时它也存在不少的缺点。如上面提到的--office word软件。假设当一个线程正在处理分页时,另一个线程就在后台进行打印操作。如果让两个线程无条件地并行,那么打印出来的文档和正在处理文档就不能同步了。这当然是我们不想看到的了。


如何创建线程:

上面已经提到,基线程(Primary Thread)如何运行的。但对于一般线程而言并没有事先设定线程入口点函数,所以在实际编程过程中我们需要调用创建线程函数CreateThread。其函数原型为:

HANDLE WINAPI CreateThread(  __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,  __in       SIZE_T dwStackSize,  __in       LPTHREAD_START_ROUTINE lpStartAddress,  __in_opt   LPVOID lpParameter,  __in       DWORD dwCreationFlags,  __out_opt  LPDWORD lpThreadId);
 

CreateThread函数创建一个线程内核对象(Thread Kernel Object);这个线程内核对象不是线程本身而是一个包含系统管理线程需要的各种数据的数据结构体(data structure)。同时系统会为该线程在进程地址空间里获取线程堆栈(Thread Stack)。

注意:在写C/C++代码时,你绝不应该直接调用CreateThread函数,而是调用_beginthreadex函数,至于原因,将稍后分析。


如何终止线程:

线程终止有四种方式:

  • 线程内函数返回(推荐方式)
  • 线程内调用ExitThread函数自杀式退出(避免这种方式)
  • 同一进程内的其他线程调用TerminateThread函数终止指定的线程(避免这种方式)
  • 包含该线程的进程自己终止了。(避免这种方式)
线程终止时:

  • 该线程拥有的所有线程对象都将被释放。
  • 该线程的退出码(exit code)从STILL_ALIVE变为ExitThread函数或TerminateThread函数的退出码。
  • 该线程内核对象(Thread Kernel Object) 变为触发状态.
  • 该线程内核对象(Thread Kernel Object) 的引用计数器减一。
  • 如果该线程是进程内的最后线程,那么进程也终止了。
当一个线程终止时,与它相关的thread kernel object直到所有外部引用都关闭的时候才自动释放。一旦一个线程不再运行。系统的其他线程不能对这个线程句柄(thread handle)进行操作,不过其他线程可用通过GetExitCodeThread函数去检验指定的线程是否退出,并得到它的退出码(Exit Code)。


线程内幕:


     调用CreateThread函数使系统创建一个线程内核对象(thread kernel object),这个对象的初始使用计数器设置为2,其他性质同时也被初始化:挂起计数(suspension count)设为1,退出码(exit code)设为STILL_ALIVE(0x103),对象设为非触发状态。

     一旦创建了内核对象,系统将会从进程地址空间中为其获取内存作为线程堆栈(thread stack)。然后写入两个值:pvParem, pfnStartAddr。

    每个线程有被称为线程上下文(thread's context)的CPU寄存器集(set of CPU registers),这些线程上下文反映了线程的最后执行时的线程CPU寄存器的状态。这些CPU寄存器记录在一个context的结构体中,这个context结构体包含在线程内核对象(thread's kernel object)中。

     在线程上下文(thread's context)中最重要的两个寄存器是指令指针寄存器(instruction pointer register)和堆栈指针寄存器(stack pointer register)。当线程内核对象初始化时,context结构体中的堆栈指针寄存器(stack pointer register)记录下pfnStartAddr在堆栈中的地址值;指令指针寄存器(instruction pointer register)记录下NTDLL.dll模块导出的RtlUserThreadStart函数地址。

     由于指令指针寄存器(instruction pointer register)设置为RtlUserThreadStart函数地址,所以RtlUserThreadStart函数是新线程开始运行的入口点,该函数的两个参数是由系统显式地写入的。新线程执行RtlUserThreadStart函数时,将会发生:

  • 建立一个结构化的异常处理帧(structured exception handling frame)以便你的线程执行过程产生的各种异常都能得到系统的默认处理。
  • 系统调用你的线程函数,把你通过CreateThread函数传递过来的pvParam参数再传递给线程函数。
  • 当你的线程函数返回时,RtlUserThreadStart函数调用ExitThread函数,同时将函数的返回值传给它。这个线程内核对象的使用计数器减一,同时线程停止执行。
  • 如果线程引起了不被处理的异常。结构化的异常处理帧(structured exception handling frame)将处理这个异常。通常这可能导致系统弹出一个消息框,用户按下按钮后,RtlUserThreadStart函数再调用ExitProcess函数终止整个进程,而不只是终止该线程。