应用篇之线程基础

基本概念

  理解线程是至关重要的,因为每个进程至少都有一个线程。我们在讨论了进程实际上有两个组成部分:一个进程内核对象和一个地址空间。类似地,线程也有两个组成部分:

  • 一个是线程的内核对象,操作系统用它管理线程。系统还用内核对象来存放线程统计信息的地方。
  • 一个线程栈,用于维护线程执行时所需的所有函数参数和局部变量。
    进程是惰性。进程从来不执行任何东西,它只是一个线程的容器。线程必然是在某个进程的上下文中创建的,而且会在这个进程内部“终其一生”。这意味着线程要在进程的地址空间内执行代码和除了数据。
      线程描述了进程内部的一条执行线路。每次初始化进程时,系统都会创建一个主线程。对于用Microsoft C/C++编译器生成的应用程序,这个线程首先会执行C/C++运行库的启动代码,然后调用入口点函数(_tmain或_tWinMain),并继续执行,直至入口点函数返回C/C++运行库的启动代码,后者最终将调用ExitProcess。对于许多应用程序来说,这个主线程是应用程序唯一需要的线程。但是进程也可以创建额外的线程来帮助它们完成自己的工作。比如多线程简化了应用程序的用户界面的设计。

创建线程函数

  关于线程函数,要注意以下几点。

  1. 在默认情况下,主线程的入口点函数必须命名为main,wmain,WinMain或wWinMain。与此不同的是,线程函数可以任意命名。
  2. 因为主线程的入口点函数有字符串参数,所有它提供了ANSI/Unicode版本供我们选择:main/wmain和WinMain/wWinMain。相反,线程函数只有一个参数,而且意义由我们(而非操作系统)来定义,因此我们不必担心ANSI/Unicode问题。
  3. 线程函数必须返回一个值,它会从成为该线程的退出代码。这类似于C/C++运行库的策略:令主线程的退出代码成为进程的推出代码。
  4. 线程函数(实际上包括所有函数)应该尽可能使用函数参数和局部变量。使用静态变量和全局变量时,多个线程可以同时访问这些变量,这样可能破坏变量中保存的内容。然而,由于函数的参数和局部变量是在线程栈上创建的。因此,不太可能被其他线程破坏。

调用CreateThread时,系统会创建一个线程内核对象。这个线程内核对象不是线程本身,而是一个较小的数据结构,操作系统用这个结构来管理线程。可以把线程内核对象想象为一个由线程统计信息构成的小型数据结构。这与进程和进程内核对象之间的关系是相同的。系统从进程的地址空间中分配内存给线程栈使用。新线程在于负责的那个线程在相同的进程下上文中运行。因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一进程中的其他所有线程的栈。这样一来,同一进程的多个线程可以很容易地互相通信。

HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
DWORD cbStackSize,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD dwCreateFlags,
PDWORD pdwThreadID);

终止运行线程

  线程可以通过以下四种方法来终止运行。

  1. 线程函数返回(这是强烈推荐的)
  2. 线程通过调用ExitThread函数“杀死”自己(要避免使用这种方法)。
  3. 同一个进程或另一个进程这种的线程调用TerminateThread函数(要避免使用这种方法)。
  4. 包含线程的进程终止运行(这种方法避免使用)。

线程函数返回

  线程函数返回是保证线程的所有资源都被正确清理的唯一方式。让线程函数返回,可以确保以下正确的应用程序清理工作都得以执行。

  • 线程函数中创建的所有C++对象都通过其析构函数被正确销毁。
  • 操作系统正确释放线程栈使用的内存。
  • 操作系统把线程的退出代码(在线程的内核对象中维护)设为线程函数的返回值。
  • 系统递减线程的内核对象的使用计数。

ExitThread函数

  为了强迫线程终止运行,可以让它调用ExitThread:

VOID ExitThread(DWORD dwExitCode);

该函数将终止线程的运行,并导致操作系统清理该线程所用的所有操作系统资源。但是,你的C/C++资源(如C++类对象)不会被销毁。

TerminateThread函数

  调用TerminateThread函数也可以“杀死”一个线程。

BOOL TerminateThread(
HANDLE hThread,
DWORD dwExitCode);

不同于ExitThread总是"杀死"主调线程(calling thread),TerminateThread能“杀死”任何线程。TerminateThread函数是异步的。也就是说,它告诉系统你想终止线程,但是在函数返回时,并不保证线程已经终止了。一个设计良好的应用程序绝不会使用这个函数,因为被终止运行的函数收不到它被“杀死”的通知。线程无法正确清理,而且不能阻止自己被终止运行。

进程终止运行时

  前面介绍的两个函数ExitProcess和TerminateProcess也可用于终止线程的运行。区别在于,这些函数会使终止运行的进程中的所有线程全部终止。同时,由于整个进程都会关闭,所以它所使用的所有资源肯定都会被清理。其中必然包括所有线程的堆栈。这两个函数会导致进程中剩余的所有线程被强行“杀死”。这意味着正确的应用程序清理工作不会被执行:C++对象的析构不会被调用,数据不会回写到磁盘等。

线程终止运行时

线程终止运行时,会发下面这些事情。

  • 线程拥有的所有用户对象句柄会被释放。在Windows中,大多数对象都是由包含了“创建这些对象的线程”的进程拥有的。但一个线程有两个用户对象:窗口(window)和挂钩(hock)。一个线程终止运行时,系统会自动销毁由线程创建或安装的任何窗口,并卸载由线程创建或安装的任何挂钩。其他对象只有在拥有线程的进程终止运行时才被销毁。
  • -线程的退出代码从STILL_ACTIVE变成传给 ExitThread或TerminateThread的代码。
  • 线程内核对象的状态变为触发状态。
  • 如果线程是进程中的最后一个活动线程,系统认为进程也终止了。
  • 线程内核对象的使用计数递减1。

猜你喜欢

转载自blog.csdn.net/webzhuce/article/details/105871166