首页 > 代码库 > 第三章--Win32程序的执行单元(部分概念及代码讲解)(上 -- 多线程)
第三章--Win32程序的执行单元(部分概念及代码讲解)(上 -- 多线程)
学习《Windows程序设计》记录
概念贴士:
1. 线程描述了进程内代码的执行路径。
2. _stdcall是新标准C/C++函数的调用方法。从底层来说,使用这种调用方法参数的进栈顺序和标准C调用(_cdecl方法)是一样的,但是_stdcall采用自动清栈的方式,而_cdecl采用的是手动清栈方式。
3. Windows规定,凡是由其负责调用的函数一律定义为_stdcall类型。ThreadProc是一个回调函数,即有Windows系统负责调用的函数,所以该函数应定义为_stdcall类型。另外,当没有显式说明时,函数默认的调用方法是_cdecl。
4. 创建新线程的函数是CreateThread,由这个函数创建的线程将在调用者的虚拟地址空间内执行。该函数执行成功后,将返回新建线程的线程句柄。
5. WaitForSingleObject函数用于等待指定的对象(hHandle)变成受信状态。其中参数dwMilliseconds给出了以毫秒为单位的要等待时间。当其值为INFINITE时,表示要等待无限长时间。
PS: WaitForSingleObject(
hThread, //要等待对象的句柄
INFINITE //要等待的时间(以毫秒为单位)
);
6. 当发生以下情况时,WaitForSingleObject函数就会返回:
1)要等待的对象变成受信(signaled)状态;
2)参数dwMilliseconds指定的时间已过去了。
PS:一个可执行对象有两种状态,未受信(nonsignaled)和受信(signaled)状态。线程对象只有当线程运行结束时才达到受信状态。
7. 当创建子进程时,如果为CreateProcess函数的bInheritHandles参数传递TRUE,那么子进程就可以继承父进程的可继承句柄。
8. dwCreationFlags--创建标志。如果为0,表示线程被创建后立即开始执行,如果指定为CREATE_SUSPENDED标志,表示线程被创建后处于挂起状态,即暂停状态,知道使用ResumeThread函数显式地启动该线程为止。
9. 线程内核对象可以说是一个包含了线程状态信息的数据结构。系统提供的管理线程的函数其实就是依靠访问线程内核对象来实现管理的。
图表: CONTEXT(上下文,即寄存器的状态)
EAX
EBX
其他CPU寄存器
Usage Count 使用计数(2)
Suspend Count 暂停次数(1)
Exi Code 退出代码(STILL_ACTIVE)
Signaled 是否受信(FALSE)
... ... ... ...
10. 线程上下文:每个线程都有着他自己的一组CPU寄存器,称为线程的上下文。
11. 使用次数:Usage Count成员记录了线程内核对象的使用次数,这个计数说明了此内核对象被打开的次数。只要线程没有结束运行,那么这个计数的值至少为1。在创建一个新的线程是,CreateThread函数返回了线程内核对象的句柄,相当于打开一次新创建的内核对象,这也会促使Usage Count的值加1。所以创建一个新的进程后,初始状态下Usage Count的值为2。之后,只要有进程打开这个内核对象,就会使得Usage Count的值加1。由于OpenThread函数的调用会灵Usage Count的值加1,所以在用完它们返回的句柄后一定要用CloseHandle函数进行关闭。(不关闭句柄,会造成内存泄漏。当然线程所在的进程结束后,该进程占用的所有资源都会统一释放。)线程函数一旦返回,线程的生命周期就到此为止。
12. 暂停次数:线程内核对象中的Suspend Count用于指明线程的暂停次数。创建线程的时候指定CREATE_SUSPENDED标志,就可以在线程有机会在执行任何代码之前改变线程的运行环境。(如优先级。)ResumeThread函数(唤醒一个被挂起的线程)会减少线程的暂停次数。注意,一个线程可以被暂停若干次。如果一个线程被暂停了3次,它必须被唤醒3次才可以分配给一个CPU。任何线程都可以调用SuspendThread函数来暂停另一个线程的运行。该函数可以增加线程的暂停次数。
13. 退出代码:成员Exit Code指定了线程的退出代码,也可以说是线程函数的返回值。同时也可以用GetExitCodeThread函数来获取线程的退出代码。
14. 是否受信:成员Signaled指定了线程对象是否为“受信”状态。线程在运行期间,Signaled的值永远是FALSE,即“未受信”。只有当线程结束后,系统才会把Signaled的值置为TRUE。
15. 线程的终止:当线程正常终止时,会发生下列事件:
1)在线程函数中创建的所有C++对象将通过它们各自的析构函数被正确地销毁。
2)该线程使用的堆栈将被释放。
3)系统将线程内核对象中Exit Code(退出代码)的值由STELL_ACTIVE设置为线程函数的返回值。
4)系统将递减线程内核对象中Usage Code(使用计数)的值。
16. 终止线程的执行有四种方法。
1)线程函数自然退出。当函数执行到return语句返回时,Windows将终止线程的执行。建议使用这种方法终止线程的执行。
2)使用ExitThread函数来终止线程。该函数会中止当前线程的进行,促使系统释放所有该线程使用的资源,但C/C++资源却不能得到正确的清楚。这里有一个有关析构函数的例子,就不展示了。
3)使用TerminateThread函数在一个线程中强制终止另一个线程的执行。注意,这是被强烈建议避免的函数。因为一旦执行这个函数,程序将无法预测目标线程在何处被终止,这造成目标线程可能根本没有机会来做清除工作,如打开文件占用的内存等都不会被释放。另外,使用该函数终止进程的时候,系统不会释放线程使用的堆栈。
4)使用ExitProcess函数来终止进程,系统会自动结束进程中所有线程的运行。但是这种方法相当于对每一个线程使用了TerminateThread函数,所以同样强烈建议避免。
PS:每一个线程都应该让它正常退出,即由它的线程函数返回。通知线程退出有很多方法,如使用事件对象、设置全局变量等。
17. 线程的优先级:每个线程都要赋予一个优先级号,取值从0(最低)到31(最高)。
18. Windows支持6个优先级类:idle、below normal、normal、above normal、high和real-time。进程属于一个优先级类,还可以为进程内的线程赋予一个相对线程优先级。线程刚被创建时,它的相对优先级总是被设置为normal。(表示解压文件时我修改了进程优先级,没感觉速度加快了啊。忧伤。)
19. SetThreadPriority(HANDLE hThread, int nPriority)函数用去设置线程优先级。参数中前者是目标线程句柄,后者定义了线程的优先级。(后面会有相关优先级例子的展示:PriorityDemo.)
20. 实际编程中改变线程优先级的常用方法:创建一个线程的时候,将CREATE_SUSPENDED标记传给了CreateThread函数,这可以使得新线程处于暂停状态。在将它的优先级设为需要的优先级。再调用ResumeThread函数恢复线程的运行。
21. WaitForMultipleObjects函数用于等待多个内核对象。(在实例PriorityDemo中最后有所展示。)
22. Windows Explorer进程中的线程就是在高优先级下运行的。(请不要联想到IE浏览器。这里的Explorer是指Windows程序管理器或者文件资源管理器,可以说是图形界面的核心。别问我为什么知道这么清楚,因为我关闭过这个进程。)
23. 在实际开发中,一般不直接使用Windows系统提供的CreateThread函数创建线程,而是使用C/C++运行期函数_beginthreadex。(注:VC默认的C/C++运行期库并不支持_beginthreadex函数。囧。)相应地,C/C++运行期库同样提供了另一个用于结束当前线程运行的函数--_endthreadex函数,用于取代ExitThread函数。
代码解释:
1.ThreadDemo
PS:主线程首先创建了一个辅助线程,打印出辅助线程的ID号,然后等待辅助线程运行结束;辅助线程仅打印出几行字符串,仅模拟真正的工作。
1 #include <stdio.h> 2 #include <windows.h> 3 4 // 线程函数 5 DWORD WINAPI ThreadProc(LPVOID lpParam) 6 { 7 int i = 0; 8 while(i < 20) 9 {10 printf(" I am from a thread, count = %d \n", i++);11 }12 return 0;13 }14 15 int main(int argc, char* argv[])16 {17 HANDLE hThread;18 DWORD dwThreadId;19 20 // 创建一个线程21 hThread = ::CreateThread (22 NULL, // 默认安全属性23 NULL, // 默认堆栈大小24 ThreadProc, // 线程入口地址(执行线程的函数)25 NULL, // 传给函数的参数26 0, // 指定线程立即运行27 &dwThreadId); // 返回线程的ID号28 printf(" Now another thread has been created. ID = %d \n", dwThreadId);29 30 // 等待新线程运行结束31 ::WaitForSingleObject (hThread, INFINITE);32 ::CloseHandle (hThread);33 return 0;34 }
2.PriorityDemo
PS:下列程序同时创建了两个线程,一个线程的优先级是“空闲”,运行的时候不断打印出"Idle Thread is running"字符串;另一个线程的优先级是“正常”,运行的时候不断打印出“Normal Thread is running”字符创。
1 #include <stdio.h> 2 #include <windows.h> 3 4 DWORD WINAPI ThreadIdle(LPVOID lpParam) 5 { 6 int i = 0; 7 while(i++<10) 8 printf("Idle Thread is running \n"); 9 10 return 0;11 }12 13 DWORD WINAPI ThreadNormal(LPVOID lpParam)14 {15 int i = 0;16 while(i++<10)17 printf(" Normal Thread is running \n");18 19 return 0;20 }21 int main(int argc, char* argv[])22 {23 DWORD dwThreadID;24 HANDLE h[2];25 26 // 创建一个优先级为Idle的线程27 h[0] = ::CreateThread(NULL, 0, ThreadIdle, NULL,28 CREATE_SUSPENDED, &dwThreadID);29 ::SetThreadPriority(h[0], THREAD_PRIORITY_IDLE);30 ::ResumeThread(h[0]);31 32 // 创建一个优先级为Normal的线程33 h[1] = ::CreateThread(NULL, 0, ThreadNormal, NULL,34 0, &dwThreadID);35 36 // 等待两个线程内核对象都变成受信状态37 ::WaitForMultipleObjects(38 2, // DWORD nCount 要等待的内核对象的数量39 h, // CONST HANDLE *lpHandles 句柄数组40 TRUE, // BOOL bWaitAll 指定是否等待所有内核对象变成受信状态41 INFINITE); // DWORD dwMilliseconds 要等待的时间42 43 ::CloseHandle(h[0]);44 ::CloseHandle(h[1]);45 46 return 0;47 }48 49 /*50 HANDLE h[2];51 h[0] = hThread1;52 h[1] = hThread2;53 DWORD dw = ::WaitForMultipleObjects(2, h, FALSE, 5000);54 switch(dw)55 {56 case WAIT_FAILED:57 // 调用WaitForMultipleObjects函数失败(句柄无效?)58 break;59 case WAIT_TIMEOUT:60 // 在5秒内没有一个内核对象受信61 break;62 case WAIT_OBJECT_0 + 0:63 // 句柄h[0]对应的内核对象受信64 break;65 case WAIT_OBJECT_0 + 1:66 // 句柄h[1]对应的内核对象受信67 break;68 }69 */
第三章--Win32程序的执行单元(部分概念及代码讲解)(上 -- 多线程)