深入浅出MFC 进程和多线程的概念
当C runtime 函数库于1970s产生的,有当时硬件水平不高,所以对于多任务还不是很普及,但是经过硬件的快速发展之后, 多任务和多线程成为主流,但是C runtime函数在多线程的表现上有严重的问题,所以C++提供了两种版本的C runtime函数库,一个给单线程的应用程序使用,一个给多线程的应用程序使用。而多线程的重大改变是,第一,变量如errno现在变成每个执行线程各拥有一个,第二,多线程版中的数据结构以同步机制加以保护
C++ 一共有六个C runtime函数库产品供你选择
Single-Threaded (static) libc.lib
Multithreaded (static) libcmt.lib
Multithreaded DLL msvcrt.lib
上面的是Release版,对于调试版,那么只需要在每个后缀后面加一个d即可
而在VS C++编译器中提供了以下的选项,有我们决定用那个c runtime函数库
/ML Single-Threaded (static)
/MT Multithreaded (static)
/MD Multithreaded DLL (dynamic import library)
加d就是Debug版本了
OS/2 Window NT Windows 95都支持多个执行线程
核心对象(PS在我看过的简版中文书中,这个是内核对象,这可能是翻译不同吧我觉得台湾语法的就像是我们粤语一样直白 其实他的英文术语叫做Kernel object,这个真是怎么翻译都不为过,只要意思接近,我们能看得懂又如何)
可以说核心对象是系统的一种资源,系统对象一旦产生,任何应用程序都可以开启并使用该对象,系统给予核心对象一个引用计数值做为管理之用,核心对象包括下列数种:
前三者用于执行线程的同步化,filemapping对象用于内存映射文件(memory mapping file ) process 和thread对象这些对象产生方式回有所不同,都会产生以handle句柄,那么其对应的计数值就回加1,核心对象的结束方式相当一致,调用CloseHandle即可
一个进程的诞生和死亡
1 shell调用CreateProcess函数**应用程序
2 应用程序在系统的引用计数+1
3 系统为应用程序创建4G内存的空间
4 应用程序所有代码和资源都会加载到这个4GB内存空间中,以及应用程序所需要的动态连接库,他们都被记录到可执行文件(PE文件格式的).idata section 中
5 系统为此进程建立一个执行线程,称为主执行线程,这个才是cpu时间的分配对象
6 系统调用C runtime函数库中的Startup code
7 在startup code中进入到应用 程序中的WinMain
8 App程序开始运作
9 使用者关闭应用程序的主窗口,结束掉消息循环
10 回到Startup code
11 回到系统,系统调用ExitProcess结束进程
可以说,透过这种方式执行起来的所有Windows程序,都是shell的子进程,本来父进程与子进程之间可以有某种关系存在,但shell在调用CreateProcess时已经把母子之间的脐带关系剪断了
CreateProcess可以用来专门**其他的程序
以下是原型
CreateProcess(
LPCSTR lpApplicationName,应用程序的路径,如果后面的后缀不是exe那么会默认设置未exe
LPSTR lpCommandLine, 需要打开的文件,例如1.txt文件,当应用程序为NULL那么就会执行此区域中的命令
LPSECURITY_ATTRIBUTES lpProcessAttributes,进程安全属性
LPSECURITY_ATTRIBUTES lpThreadAttributes,线程安全属性
BOOL bInheritHandles,继承父进程的安全属性
DWORD dwCreationFlags,创建的标志
LPVOID lpEnvironment,环境通常未NULL表示使用父进程的环境
LPCSTR lpCurrentDirectory,当前文件的工作目录,NULL表示使用父进程的工作目录
LPSTARTUPINFO lpStartupInfo,包含许多与windows外貌相关的属性
LPPROCESS_INFORMATION lpProcessInformation包含新创建进程和线程ID和句柄
);
正常来说,如果一个进程想要结束自己生命的话,只需用到ExiteProcess(UINT fuExitCode)
如果一个进程想借宿另一个进程的生命,可以使用
BOOL TerminateProcess(HANDLE hProcess,UINT fuExitCode);
但是使用TerminateProcess函数的话,就不会通知该进程所使用的所有DLLs
如果想要子进程单独出来,只要来CreateProcess函数之后及时关闭新创建进程和线程的句柄。
一个执行线程的诞生与死亡
程序代码的执行是执行线程的工作,当一个进程建立起来的时候,主执行线程也产生,所以没有windows程序一开会有一个执行线程。如果想要其他线程进程额外的工作,那么只要调用CreateThread函数即可
1 配置 执行线程对象,其handle将成为CreateThread的传回值
2 其设定计数值+1
3 配置执行线程的Context
4 保留执行线程的堆栈
5 讲context中的堆栈指针缓存器和指令指针缓存器(IP)设定妥当
所谓工作切换(context switch)其实就是对执行线程context的切换
创建线程API原型如下
CreateThread (LPSECURITY_ATTRIBUTES lpThreadAttributes,线程的安全属性一般设置为NULL
DWORD dwStackSize,堆栈的大小,
LPTHREAD_START_ROUTINE lpStartAddress,执行线程函数名称,
LPVOID lpParameter,执行线程函数中的参数,可以类型转换因为传递的是一个void指针,这个就相当于C#的object类,
DWORD dwCreationFlags,这个参数如果是0,那么线程立即执行,如果是CREATE_SUSPENDED则要求执行线程暂停执行,(那么我们必须调用ResumeThread才能唤醒线程)
LPDOWRD lpThreadId回传一个执行线程ID
)
执行线程的结束有两种情况,一种是寿终正寝,是执行线程函数正常结束退出,这时候系统会调用ExitThread做一些善后的工作,一种是未得善终,对于一个执行线程是无穷循环的情况可以使用TerminateThread函数结束此执行线程,但是使用这个函数会不能正确地释放线程所使用的资源和无法通知使用他的DLLs
以_beginthreadex取代CreateThread
在程序开发中很难避免调用一个C runtime函数,那么为了保证多线程情况下的安全,C runtime 函数库必须为每一个执行线程做一些工作,没有这些工作C runtime函数库就不知道要为每一个执行线程配置一块新的内存,作为执行线程的区域变量使用,所以CreateThread有一个叫做_beginthreadex的外包函数,负责额外的工作,虽然_beginthreadex函数不需要windows.h的头文件,但是还是要使用CloseHandle关闭执行线程,但是CloseHandle是需要包含windows头文件,所以我决定这是一个自相矛盾的。
_beginthreadex函数的原型如下:
unsigned long _beginthreadex(
void * security,
unsigned stack_size,
unsigned (__stdcall *start_address)(void* ),
void * arglist,
unsigned initflag,
unsigned * thrdaddr
)
换句话说,_beginthreadex和CreateThread的传回值是相同的,但是前者另外设立了errno和doserrno
针对WIN32 API ExitThread 也有一个对应的C runtime函数:_endthreadex,只需要一个前面所创建的线程id
执行线程优先权
优先权是排程的重要依据,优先权高的执行线程,永远先得到CPU的青眯,但是操作系统会根据用户的操作来调整程序的优先级,例如前景程序的优先级会比背景程序的优先级会低,一下就是线程优先级的参数值表示
一般来说程序默认会指定NORMAL_PRIORITY_CLASS属性的程序优先级,但是这个有一个例外就是如果父进程是IDLE_PRIORITY_CLASS那么子进程就只能跟随父进程的线程优先级属性
idle 等级只有在CPU时间将被浪费掉时才执行,此等级最适合于系统监视软件,或屏幕保护软件
normal是预设等级,系统可以动态改变优先权,但是只限制normal等级当进程变成前景,执行线程优先权会提升为9.当进程变成背景,优先级会降低为7
high是为了立即反应的需要,例如使用者按下Ctrl+Esc时立即把工作管理器(task manager)带出场
realtime等级几乎不会被一般应用程序使用,就连系统控制鼠标,键盘,磁盘状态重新扫描,弹出任务管理器等的执行线程都比realtime优先级还低。这种等级使用在如果不再某个时间范围没被执行的话,资料就要遗失的情况,这个等级一定得在正确评估之下使用之,如果你把这样的等级指定给一般(并不会常常被阻塞的)执行线程,多任务环境恐怕会瘫痪,因为这个执行线程有如此高的优先权,其他执行线程再没有寄回被执行
上面的四种等级,每个等级又映射到某一范围的优先权值,IDLE最低,NORMAL次之,HIGH_又次之,REALTIME_最高,在每个等级中,可以使用SetThreadPriority设定精确的优先权,并且可以稍高或稍低于giant等级的正常值(范围是两个点数)