友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
深入浅出MFC第2版(PDF格式)-第13部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
_
常数中比较常用的是 ,它会使得子进程产生之后,其主执行线程立
刻被暂停执行。
第七个参数lpE nvironment 可以指定进程所使用的环境变量区。通常我们会让子进程继
承父进程的环境变量,那么这里要指定NULL 。
第八个参数lpCur rentDirectory 用来设定子进程的工作目录与工作磁盘。如果指定
NULL ,子进程就会使用父进程的工作目录与工作磁盘。
第九个参数lpSt artupInfo 是一个指向STAR TUPINF O 结构的指针。这是一个庞大的结
构,可以用来设定窗口的标题、位置与大小,详情请看API 使用手册。
PROCESS INF ORMA TION
_
最后一个参数是一个指向 结构的指针:
42
…………………………………………………………Page 105……………………………………………………………
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
当系统为我们产生「进程对象」和「执行线程对象」,它会把两个对象的handle 填入此结
构的相关字段中,应用程序可以从这里获得这些handles 。
如果一个进程想结束自己的生命,只要调用:
VOID ExitProcess(UINT fuExitCode);
就可以了。如果进程想结束另一个进程的生命,可以使用:
BOOL TerminateProcess(HANDLE hProcess; UINT fuExitCode);
很显然,只要你有某个进程的handle,就可以结束它的生命。TerminateProcess 并不被
建议使用,倒不是因为它的权力太大,而是因为一般进程结束时,系统会通知该进程所
开启(所使用)的所有DLLs,但如果你以TerminateProcess 结束一个进程,系统不会
做这件事,而这恐怕不是你所希望的。
前面我曾说过所谓割断脐带这件事情,只要你把子进程以CloseHandle 关闭,就达到了
目的。下面是个例子:
PROCESS_INFORMATION ProcInfo;
BOOL fSuccess;
fSuccess = CreateProcess(。。。;&ProcInfo);
if (fSuccess) {
CloseHandle (ProcInfo。hThread);
CloseHandle (ProcInfo。hProcess);
}
43
…………………………………………………………Page 106……………………………………………………………
一个执行线程的诞生与死亡
程序代码的执行,是执行线程的工作。当一个进程建立起来,主执行线程也产生。所以每一个
Windows 程序一开始就有了一个执行线程。我们可以调用CreateThread 产生额外的执行
线程,系统会帮我们完成下列事情:
1。 配置「执行线程对象」,其handle 将成为CreateThread 的传回值。
1
2。 设定计数值为 。
3。 配置执行线程的context 。
4。 保留执行线程的堆栈。
context SS IP
5。 将 中的堆栈指针缓存器( )和指令指针缓存器( )设定妥当。
看看上面的态势,的确可以显示出执行线程是CPU 分配时间的单位。所谓工作切换(context
switch )其实就是对执行线程的context 的切换。
程序若欲产生一个新执行线程,调用CreateThread 即可办到:
CreateThread (LPSECURITY_ATTRIBUTES lpThreadAttributes;
DWORD dwStackSize;
LPTHREAD_START_ROUTINE lpStartAddress;
LPVOID lpParameter;
DWORD dwCreationFlags;
LPDWORD lpThreadId
);
第一个参数表示安全属性的设定以及继承,请参考API 手册。Windows 95 忽略此一参
数。第二个参数设定堆栈的大小。第三个参数设定「执行线程函数」名称,而该函数的参
0
数则在这里的第四个参数设定。第五个参数如果是 ,表示让执行线程立刻开始执行,如
CREA TE SUSPENDED
_
果是 , 则是要求执行线程暂停执行( 那么我们必须调用
ResumeThread 才能令其重新开始)。最后一个参数是个指向D WORD 的指针,系统会
把执行线程的ID 放在这里。
上面我所说的「执行线程函数」是什么?让我们看个实例:
44
…………………………………………………………Page 107……………………………………………………………
VOID ReadTime(VOID);
HANDLE hThread;
DWORD ThreadID;
hThread = CreateThread(NULL; 0; (LPTHREAD_START_ROUTINE)ReadTime;
NULL; 0; &ThreadID);
。。。
//…………………………………………………………………………………………………………………………………………………………………………………
// thread 函数 。
// 不断利用 GetSystemTime 取系统时间 ,
// 并将结果显示在对话框 _hhwndDlg 的 IDE_TIMER 栏位上 。
//…………………………………………………………………………………………………………………………………………………………………………………
VOID ReadTime (VOID)
{
char str'50';
SYSTEMTIME st;
while(1) {
GetSystemTime(&st);
sprintf(str;〃%u:%u:%u〃; st。wHour; st。wMinute; st。wSecond);
SetDlgItemText (_hWndDlg; IDE_TIMER; str);
Sleep (1000); // 延迟一秒 。
}
}
当CreateThread 成功,系统为我们把一个执行线程该有的东西都准备好。执行线程的主体在
哪里呢?就在所谓的执行线程函数。执行线程与执行线程之间,不必考虑控制权释放的问题,
因为Win32 操作系统是强制性多任务。
执行线程的结束有两种情况,一种是寿终正寝,一种是未得善终。前者是执行线程函数正常
结束退出,那么执行线程也就自然而然终结了。这时候系统会调用ExitThread 做些善后清
理工作(其实执行线程中也可以自行调用此函数以结束自己)。但是像上面那个例子,执
行线程根本是个无穷循环,如何终结?一者是进程结束(自然也就导至执行线程的结束),
二者是别的执行线程强制以TerminateThread 将它终结掉。不过,TerminateThread 太过毒
辣,非必要还是少用为妙(请参考API 手册)。
45
…………………………………………………………Page 108……………………………………………………………
以_beginthreadex 取代CreateThread
别忘了Windows 程序除了调用Win32 API ,通常也很难避免调用任何一个C runtime 函
数。为了保证多线程情况下的安全,C runtime 函数库必须为每一个执行线程做一些簿记工
作。没有这些工作,C runtime 函数库就不知道要为每一个执行线程配置一块新的内存,
做为执行线程的区域变量用。因此,CreateThread 有一个名为_beginthreadex 的外包函数,
负责额外的簿记工作。
请注意函数名称的底线符号。它必须存在,因为这不是个标准的ANSI C runtime 函数。
_beginthreadex 的参数和CreateThread 的参数其实完全相同,不过其型别已经被「净化」
了,不再有Win32 型别包装。这原本是为了要让这个函数能够移植到其它操作系统,因
为微软希望_beginthreadex 能够被实作于其它平台,不需要和Windows 有关、不需要
包含windows。h 。但实际情况是,你还是得调用CloseHandle 以关闭执行线程, 而
CloseHandle 却是个Win32 API ,所以你还是需要包含windows。h 、还是和Windows 脱
离不了关系。微软空有一个好主意,却没能落实它。
把_beginthreadex 视为CreateThread 的一个看起来比较有趣的版本,就对了:
unsigned long _beginthreadex (
void *security;
unsigned stack_size;
unsigned (__stdcall *start_address)(void *);
void *arglist;
unsigned initflag;
unsigned* thrdaddr
);
_beginthreadex 所传回的unsigned long 事实上就是一个Win32 HANDLE ,指向新执行
线程。换句话说传回值和CreateThread 相同,但_beginthreadex 另外还设立了errno 和
doserrno 。
下面是一个最简单的使用范例:
#0001 #include
#0002 #include
46
…………………………………………………………Page 109……………………………………………………………
#0003 unsigned __stdcall myfunc(void* p);
#0004
#0005 void main()
#0006 {
#0007 unsigned long thd;
#0008 unsigned tid;
#0009
#0010 thd = _beginthreadex(NULL;
#0011 0;
#0012 myfunc;
#0013 0;
#0014 0;
#0015 &tid );
#0016 if (thd != NULL)
#0017 {
#0018 CloseHandle(thd);
#0019 }
#0020 }
#0021
#0022 unsigned __stdcall myfunc (void* p)
#0023 {
#0024 // do your job。。。
#0025 }
针对Win32 API ExitThread ,也有一个对应的C runtime 函数:_endthreadex 。它只需要
一个参数,就是由_beginthreadex 第6个参数传回来的ID 值。
关于_beginthreadex 和_endthreadex ,以及执行线程的其它各种理论基础、程序技术、使
用技巧,可参考由Jim Beveridge & Robert Wiener 合着,Addison Wesley 出版的
Multithreading Applications in Win32 Win32 / /
一书( 多线程程序设计 侯俊杰译 峰出
版)。
47
…………………………………………………………Page 110……………………………………………………………
执行线程优先权 ( y)
Priorit
优先权是排程的重要依据。优先权高的执行线程,永远先获得CPU 的青睐。当然啦,操作
系统会视情况调整各个执行线程的优先权。例如前景执行线程的优先权应该调高一些,背
景执行线程的优先权应该调低一些。
0 31
执行线程的优先权范围从 (最低)到 (最高)。当你产生执行线程,并不是直接以数值
指定其优先权,而是采用两个步骤。第一个步骤是指定「优先权等级(Priority Class )」
给进程,第二步骤是指定「相对优先权」给该进程所拥有的执行线程。图1…7 是优先权等
级的描述,其中的代码在CreateProcess 的dwCreationFlags 参数中指定。如果你不指
NORMAL PRIORIT Y CLASS …
_ _
定, 系统预设给的是 除非父进程是
IDLE PRIORIT Y CLASS IDLE PRIORIT Y CLASS
_ _ _ _
(那么子进程也会是 )。
等级 代码 优先权值
idle IDLE_PRIORITY_CLASS 4
9 7
normal NORMAL_PRIORITY_CLASS (前景)或 (背景)
high HIGH_PRIORITY_CLASS 13
realtime REALTIME_PRIORITY_CLASS 24
图 1…7 Win32 执 行 线 程 的优先权等级划分
■ 〃idle〃 CPU
等级只有在 时间将被浪费掉时(也就是前一节所说的空闲时间)
才执行。此等级最适合于系统监视软件,或屏幕保护软件。
■ 〃normal〃 〃normal〃
是预设等级。系统可以动态改变优先权,但只限于 等级。
9 7
当进程变成前景,执行线程优先权提升为 ,当进程变成背景,优先权降低为 。
■ 〃high〃 Ctrl+Esc
等级是为了立即反应的需要,例如使用者按下 时立刻把工作管
理器(task manager )带出场。
■ 〃realtime〃
等级几乎不会被一般应用程序使用。就连系统中控制鼠标、键盘、
48
…………………………………………………………Page 111……………………………………………………………
磁盘状态重新扫描、Ctrl+Alt+Del 等的执行线程都比〃realtime〃 优先权还低。这
种等级使用在「如果不在某个时间范围内被执行的话,资料就要遗失」的情况。
这个等级一定得在正确评估之下使用之,如果你把这样的等级指定给一般的
(并不会常常被阻塞的)执行线程,多任务环境恐怕会瘫痪,因为这个执行线程有如
此高的优先权,其它执行线程再没有机会被执行。
IDLE NORMAL
上述四种等级,每一个等级又映射到某一范围的优先权值。 _ 最低, _ 次
之,HIGH_ 又次之,REAL TIME_ 最高。在每一个等级之中,你可以使用SetThreadPriority
设定精确的优先权,并且可以稍高或稍低于该等级的正常值(范围是两个点数)。你可
SetThreadPriority
以把 想象是一种微调动作。
SetThreadPriority 的参数 微调幅度
THREAD PRIORITY LOWEST
_ _ …2
THREAD_PRIORITY_BELOW_NORMAL …1
THREAD_PRIORITY_NORMAL 不变
THREAD_PRIORITY_ABOVE_NORMAL +1
THREAD_PRIORITY_HIGHEST +2
除了以上五种微调,另外还可以指定两种微调常数:
SetThreadPriority 的参数 面对任何等级 面对〃realtime〃等级
的调整結果 : 的调整結果 :
THREAD_PRIORITY_IDLE 1 16
THREAD_PRIORITY_TIME_CRITICAL 15 31
这些情况可以以图1…8 作为总结。
49
…………………………………………………………Page 112……………………………………………………………
优先权等级 idle lowest below normal normal above normal highest time critical
idle 1 2 3 4 5 6 15
normal (背景) 1 5 6 7 8 9 15
normal (前景) 1 7 8 9 10 11 15
high 1 11 12 13 14 15 15
realtime 16 22 23 24 25 26 31
图 执行线程优先权
1…8 Win32
多线程程序设计实例
我设计了一个MltiThrd 程序,放在书附盘片的MltiThrd。01 子目录中。这个程序一开始
…2 …1 0 + 1 +2
产生五个执行线程,优先权分别微调 、 、 、 、 ,并且虚悬不执行:
HANDLE _hThread'5'; // global variable
。。。
LONG APIENTRY MainWndProc (HWND hWnd; UINT message;
UINT wParam; LONG lParam)
{
DWORD ThreadID'5';
static DWORD ThreadArg'5' = {HIGHEST_THREAD; // 0x00
ABOVE_AVE_THREAD; // 0x3F
NORMAL_THREAD; // 0x7F
BELOW_AVE_THREAD; // 0xBF
LOWEST_
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!