概念:
进程由两部分组成:一个进程内核对象,一个地址空间(进程只是线程的容器)
类似,线程由两部分组成:
一个线程内核对象,用于管理线程,还用于存放线程统计信息。
一个线程栈,用于线程执行时所需的所有函数参数以及局部变量。(线程栈从进程的内存空间分赔得到)
注意:线程内核对象并不是线程本身,它是一个存储线程统计信息和管理的小型数据结构。
线程是在进程的地址空间执行代码,处理数据,共享进程的内核对象句柄(句柄表是针对每个进程的),因此当进程中有多个线程同时执行时,就可能出现“数据共享”的相关问题。由于线程比进程开销的资源少得多,因此应尽量用一个线程而不是新开一个进程来处理问题。
6.1 何时创建线程
基本流程:
初始化进程——>系统创建一个主线程——>执行C/C++运行库的启动代码——>调用主线程的入口点函数(_tmain或_twinmain)——>、、、、——>入口点函数返回运行库的启动代码——>最终调用ExitProcess。
多线程简化了应用程序的用户界面设计;将应用程序设计为多线程更易于扩展。
6.2 何时不应该创建线程
在几乎所有应用程序中,所有界面组件(窗口)都应该共享一个线程,一个窗口的所有子窗口都应该由一个线程创建。用户界面的优先级应该高于其他的工作线程,用户界面才能快速响应用户的操作。
6.3 编写第一个线程函数
每个线程都必须有一个入口点函数,这是线程的执行起点。
创建辅助线程,必须有自己的入口点函数,形式如下:
DWORD WINAPI TreadFun(PVOID proparam) { //只有一个参数
Dword dwResult=0;
、、、
return dwResult;
}
线程函数可以执行任何任务,最终线程函数终止并返回,此时线程将终止运行,线程的内存也将释放,线程内核计数递减。
注意: 1、与主线程入口点函数不同,线程函数可以任意命名。
2、线程函数只有一个参数,其意义有我们定义。
3、线程函数必须返回一个值,成为线程的退出代码(主线程的退出代码成为进程的退出代码)
4、线程函数应尽可能使用函数参数或局部变量(都是在线程栈上创建的),而避免使用全局变量(多个线程同时访问时,可能造成数据破坏)。
6.4 CreateThread函数
由一个正在运行的线程调用该函数来创建一个新线程,此时系统会创建一个线程内核对象。系统从进程的地址空间分配内存给线程栈使用。新线程可以访问进程的所有内核对象句柄、进程的所有内存空间以及该进程其他线程的栈,因此同一进程的多线程间很容易实现通信。
注意:CreateThread函数是Windows的api函数,在编写C/C++代码时必须选择使用编译器运行库提供的相应功能的函数:_beginthreadex函数。 在CreateThread函数(或相应替代函数)的参数列表中传入线程的入口点函数指针以及其参数。
Windows是一个抢占式的多线程系统。即新线程和调用CreateThread函数的线程可以同时执行,所以可能出现问题。 可以设置CreateThread函数的dwCreateFeags参数:设为0 ,表示线程创建即可以进行调度。设为CREATE_SUSPENDED,系统创建并初始化线程,但会暂停线程的运行。
6.5 终止线程运行
四种终止方式:
1、线程函数返回(强烈推荐)
2、线程调用ExitThread函数“杀死”线程(避免)
3、同一进程或其他进程的另一线程调用TerminateThread函数(避免)
4、包含线程的进程终止运行(避免)。
始终都应该将线程设计线程函数返回(强烈推荐)的形式,即当想要线程终止运行时,它们就能够返回。这是
确保所有线程资源被正确地清除的唯一办法。
如果线程能够返回,就可以确保下列事项的实现:
• 在线程函数中创建的所有C + +对象均将通过它们的撤消函数正确地撤消。
• 操作系统将正确地释放线程堆栈使用的内存。
• 系统将线程的退出代码(在线程的内核对象中维护)设置为线程函数的返回值。
• 系统将递减线程内核对象的使用计数。
ExitThread函数:
可以让线程调用E x i t T h r e a d函数,以便强制线程终止运行:
该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,
C + +资源(如C + +类对象)将不被撤消。由于这个原因,最好从线程函数返回,而不是通过调
用E x i t T h r e a d来返回(详细说明参见第4章)。
当然,可以使用E x i t T h r e a d的d w E x i t T h r e a d参数告诉系统将线程的退出代码设置为什么。
E x i t T h r e a d函数并不返回任何值,因为线程已经终止运行,不能执行更多的代码。
注意: 终止线程运行的最佳方法是让它的线程函数返回。但是,如果使用本节介绍的
方法,应该知道E x i t T h r e a d函数是Wi n d o w s用来撤消线程的函数。如果编写C / C + +代码,
那么决不应该调用E x i t T h r e a d。应该使用Visual C++运行期库函数_ e n d t h r e a d e x。如果
不使用M i c r o s o f t的Visual C++编译器,你的编译器供应商有它自己的E x i t T h r e a d的替
代函数。不管这个替代函数是什么,都必须使用。本章后面将说明_ e n d t h r e a d e x的作
用和它的重要性。
TemnnateThread函数:
与E x i t T h r e a d不同,E x i t T h r e a d总是撤消调用的线程,而Te r m i n a t e T h r e a d能够撤消任何线程。
h T h r e a d参数用于标识被终止运行的线程的句柄。当线程终止运行时,它的退出代码成为你作
为d w E x i t C o d e参数传递的值。同时,线程的内核对象的使用计数也被递减。
注意Te r m i n a t e T h r e a d函数是异步运行的函数,也就是说,它告诉系统你想要线程终
止运行,但是,当函数返回时,不能保证线程被撤消。如果需要确切地知道该线程已
经终止运行,必须调用Wa i t F o r S i n g l e O b j e c t (第9章介绍)或者类似的函数,传递线程的
句柄。
设计良好的应用程序从来不使用这个函数,因为被终止运行的线程收不到它被撤消的通知。
线程不能正确地清除,并且不能防止自己被撤消。
注意当使用返回或调用E x i t T h r e a d的方法撤消线程时,该线程的内存堆栈也被撤消。
但是,如果使用Te r m i n a t e T h r e a d,那么在拥有线程的进程终止运行之前,系统不撤消该
线程的堆栈。M i c r o s o f t故意用这种方法来实现Te r m i n a t e T h r e a d。如果其他仍然正在执行
的线程要引用强制撤消的线程堆栈上的值,那么其他的线程就会出现访问违规的问题。
如果将已经撤消的线程的堆栈留在内存中,那么其他线程就可以继续很好地运行。
线程终止运行时发生的操作
当线程终止运行时,会发生下列操作:
• 线程拥有的所有用户对象均被释放。在Wi n d o w s中,大多数对象是由包含创建这些对象
的线程的进程拥有的。但是一个线程拥有两个用户对象,即窗口和挂钩。当线程终止运
行时,系统会自动撤消任何窗口,并且卸载线程创建的或安装的任何挂钩。其他对象只
有在拥有线程的进程终止运行时才被撤消。
• 线程的退出代码从S T I L L A C T I V E改为传递给E x i t T h r e a d或Te r m i n a t e T h r e a d的代码。
• 线程内核对象的状态变为已通知。
• 如果线程是进程中最后一个活动线程,系统也将进程视为已经终止运行。
• 线程内核对象的使用计数递减1。
当一个线程终止运行时,在与它相关联的线程内核对象的所有未结束的引用关闭之前,该
内核对象不会自动被释放。
一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调
用G e t E x i t c o d e T h r e a d来检查由h T h r e a d标识的线程是否已经终止运行。