第三章--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号,然后等待辅助线程运行结束;辅助线程仅打印出几行字符串,仅模拟真正的工作。
#include <stdio.h>
#include <windows.h> // 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
int i = ;
while(i < )
{
printf(" I am from a thread, count = %d \n", i++);
}
return ;
} int main(int argc, char* argv[])
{
HANDLE hThread;
DWORD dwThreadId; // 创建一个线程
hThread = ::CreateThread (
NULL, // 默认安全属性
NULL, // 默认堆栈大小
ThreadProc, // 线程入口地址(执行线程的函数)
NULL, // 传给函数的参数
, // 指定线程立即运行
&dwThreadId); // 返回线程的ID号
printf(" Now another thread has been created. ID = %d \n", dwThreadId); // 等待新线程运行结束
::WaitForSingleObject (hThread, INFINITE);
::CloseHandle (hThread);
return ;
}
2.PriorityDemo
PS:下列程序同时创建了两个线程,一个线程的优先级是“空闲”,运行的时候不断打印出"Idle Thread is running"字符串;另一个线程的优先级是“正常”,运行的时候不断打印出“Normal Thread is running”字符创。
#include <stdio.h>
#include <windows.h> DWORD WINAPI ThreadIdle(LPVOID lpParam)
{
int i = ;
while(i++<)
printf("Idle Thread is running \n"); return ;
} DWORD WINAPI ThreadNormal(LPVOID lpParam)
{
int i = ;
while(i++<)
printf(" Normal Thread is running \n"); return ;
}
int main(int argc, char* argv[])
{
DWORD dwThreadID;
HANDLE h[]; // 创建一个优先级为Idle的线程
h[] = ::CreateThread(NULL, , ThreadIdle, NULL,
CREATE_SUSPENDED, &dwThreadID);
::SetThreadPriority(h[], THREAD_PRIORITY_IDLE);
::ResumeThread(h[]); // 创建一个优先级为Normal的线程
h[] = ::CreateThread(NULL, , ThreadNormal, NULL,
, &dwThreadID); // 等待两个线程内核对象都变成受信状态
::WaitForMultipleObjects(
, // DWORD nCount 要等待的内核对象的数量
h, // CONST HANDLE *lpHandles 句柄数组
TRUE, // BOOL bWaitAll 指定是否等待所有内核对象变成受信状态
INFINITE); // DWORD dwMilliseconds 要等待的时间 ::CloseHandle(h[]);
::CloseHandle(h[]); return ;
} /*
HANDLE h[2];
h[0] = hThread1;
h[1] = hThread2;
DWORD dw = ::WaitForMultipleObjects(2, h, FALSE, 5000);
switch(dw)
{
case WAIT_FAILED:
// 调用WaitForMultipleObjects函数失败(句柄无效?)
break;
case WAIT_TIMEOUT:
// 在5秒内没有一个内核对象受信
break;
case WAIT_OBJECT_0 + 0:
// 句柄h[0]对应的内核对象受信
break;
case WAIT_OBJECT_0 + 1:
// 句柄h[1]对应的内核对象受信
break;
}
*/
第三章--Win32程序的执行单元(部分概念及代码讲解)(上 -- 多线程)的更多相关文章
- 第二章--Win32程序运行原理 (部分概念及代码讲解)
学习<Windows程序设计>记录 概念贴士: 1. 每个进程都有赋予它自己的私有地址空间.当进程内的线程运行时,该线程仅仅能够访问属于它的进程的内存,而属于其他进程的内存被屏蔽了起来,不 ...
- 《算法》第三章部分程序 part 6
▶ 书中第三章部分程序,加上自己补充的代码,包含双向索引表.文建索引.稀疏向量类型 ● 双向索引表 package package01; import edu.princeton.cs.algs4.S ...
- 《算法》第三章部分程序 part 5
▶ 书中第三章部分程序,加上自己补充的代码,包含公共符号表.集合类型 ● 公共符号表,用于普通查找表的基本类 package package01; import java.util.NoSuchEle ...
- 《算法》第三章部分程序 part 4
▶ 书中第三章部分程序,加上自己补充的代码,包括散列表.线性探查表 ● 散列表 package package01; import edu.princeton.cs.algs4.Queue; impo ...
- 《算法》第三章部分程序 part 3
▶ 书中第三章部分程序,加上自己补充的代码,红黑树 ● 红黑树,大部分方法与注释与二叉树相同 package package01; import java.util.NoSuchElementExce ...
- 《算法》第三章部分程序 part 2
▶ 书中第三章部分程序,加上自己补充的代码,平衡二叉搜索树 ● 平衡二叉搜索树 package package01; import java.util.NoSuchElementException; ...
- 《算法》第三章部分程序 part 1
▶ 书中第三章部分程序,加上自己补充的代码,包括单词频率统计,(单链表)顺序查找表,二分查找表 ● 单词频率统计 package package01; import edu.princeton.cs. ...
- Delphi 如何在程序中执行动态生成的Delphi代码
如何在程序中执行动态生成的Delphi代码 经常发现有人提这类问题,或者提问内容最后归结成这种问题 前些阵子有位高手写了一个“执行动态生成的代码”,这是真正的高手,我没那种功力,我只会投机取巧. 这里 ...
- 如何在程序中执行动态生成的Delphi代码
如何在程序中执行动态生成的Delphi代码 经常发现有人提这类问题,或者提问内容最后归结成这种问题 前些阵子有位高手写了一个“执行动态生成的代码”,这是真正的高手,我没那种功力,我只会投机取巧. 这里 ...
随机推荐
- 详细介绍windows下使用python pylot进行网站压力测试
windows下使用python进行网站压力测试,有两个必不可少的程序需要安装,一个是python,另一个是pylot.python是一个安装软件,用来运行python程序,而pylot则是pytho ...
- Hybris电商方案介绍(企业全渠道) B2B B2C O2O建设
1). 什么是Hybris: hybris software成立于1997年,2013年与SAP整合,成为SAP旗下的一份子,提供全渠道客户互动与商务解决方案,该解决方案能够为各机构提供客户的实时背景 ...
- Cubieboard2裸机开发之(五)看门狗操作
前言 说到看门狗,应该不会陌生,看门狗说白了就是一个定时器,但是它有一个非常重要的功能就是复位系统.在A20里,看门狗的操作非常简单,只有两个寄存器,不需要操作时钟相关的东西,系统起来后可以直接使用, ...
- Android-NDK编译:cocos2d-x(三) eclipse 导入工程
NDK 编译后,用eclipse导入cocos2d-x工程 菜单[File]-->[New]-->[Project] ,弹出New Project 对话框 窗口下方 选 [Android] ...
- 重构第1天:封装集合(Encapsulate Collection)
理解:封装集合就是把集合进行封装,只提供调用者所需要的功能行借口,保证集合的安全性. 详解:在大多的时候,我们没有必要把所有的操作暴露给调用者,只需要把调用者需要的相关操作暴露给他,这种情况中下我们就 ...
- c和c++关于const的一些区别
以下参考了网上的一些资料并通过程序验证. 注意,以下情况都是用gcc和g++编译器得到的结果,用vs编译器又会有所不同. 以下说下c和c++中const定义的常量的一些区别: c++中用const定义 ...
- coreseek 提示 client version is higher than daemon version 解决办法
安装好coreseek,开启了服务之后,通过 sphinx php扩展去请求数据,提示:client version is higher than daemon version (client is ...
- jdbc根据实例名 连接 sql server
jdbc:sqlserver://PC;instanceName=sql2012;databaseName=xxxxx
- 关于异步Promises
英文原文:What's The Point Of Promises? 迄今为止,可能每个JavaScript开发者和他们的祖母都听说过Promises.如果你没有,那么你即将会.promises的概念 ...
- sqlserver -- 学习笔记(七)获取同组数据的前两条记录
不啰嗦,直接上图,大概实现效果如下: 有上面这样一份数据,将他们按照userAccount和submitTime进行分组,然后提前每组数据的前两条记录 提取后数据如下: 实现的SQL如下: selec ...