WinAPI 线程的优先级 调度 亲缘性
每个线程都拥有一个 上下文结构 , 这个结构维护在线程的内核对象中 。 这个上下文结构反应了线程上次运行时该线程的 CPU 寄存器的状态 。 每隔 20ms 左右 , Windows 要查看当前存在的所有线程内核对象 。 在这些对象中 , 只有某些对象被视为可以调度的对象 。Windows 选择可调度的线程内核对象中的一个 , 将它加载到 CPU 的寄存器中 , 它的值是上次保存在线程的环境中的值 。 这项操作成为 上下文切换 。
暂停和恢复线程的运行 :
在线程内核对象的内部有一个值 , 用于指明线程的暂停计数 。当调用 CreateProcess 或 CreateThread 函数时 ,就创建了线程的内核对象 , 并且它的暂停计数是 1 , 这可以防止线程被调度到 CUP 中 , 因为线程的初始化需要时间 , 不希望在系统做好充分准备前就开始执行线程 。
当线程完成初始化好了之后 , CreateProcess 或 CreateThread 要查看是否已经传递了 CREATE_SUSPENDED 标志 。 如果传递了该标志 , 那么创建函数返回 , 新线程处于 暂停状态 。 如果尚未传递该标志 , 那么该函数将线程的暂停计数减为 0 。除非线程正在等待其他某种事情的发生 , 否则该线程就处于可调度状态 。
ResumeThread 函数 : 运行指定的线程
DWORD ResumeThread( HANDLE hThread ); //参数是要运行的线程句柄 , 如果运行成功返回线程的前一个暂停计数 , 否则返回 0xFFFFFFFF 。
SuspendThread 函数 : 任何线程都可以调用该函数来暂停另一个线程的运行 。
DWORD SuspendThread( HANDLE hThread ); //参数是要运行的线程句柄 , 如果运行成功返回线程的前一个暂停计数 , 否则返回 0xFFFFFFFF 。
SuspendThread 与内涵方式的执行时异步进行的 , 但是在线程恢复运行之前 , 不会发生用户方式的执行
在实际环境中 , 调用 SuspendThread 时必须小心 , 因为不知道暂停线程运行时它在进行什么操作 。如果线程正在试图从堆栈中分配内存 , 那么该线程将在堆栈上设置一个锁 。当其他线程试图访问堆栈时 , 这些线程的访问就被停止 , 直到第一个线程恢复运行 。只有确切知道目标线程是什么或者目标线程正在做什么 , 并且采取强有力的措施来避免因暂停线程的运行而带来的问题或死锁状态 。 SuspendThread
才是安全的 。
暂停和恢复进程的运行 :
对 Windows 来说 , 不存在暂停或恢复进程的概念 , 因为进程从来不会被安排获得 CPU 时间 。
CreateToolhelp32Snapshot : 函数 //为指定的进程 , 进程使用的堆[HEAP] , 模块[MODULE] , 线程[THREAD] 建立一个快照[snapshot]
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags , //指定快照中包含的系统内容 。
DWORD th32ProcessID //指定将要快照的进程ID , 如果该参数传递 0 表示快照当前进程
);
Thread32First : 函数 //查找指定进程中的第一条线程
返回TRUE,如果该线程列表的第一项已被复制到缓冲区,否则返回false。该ERROR_NO_MORE_FILES错误值是由GetLastError函数返回,如果没有线程存在或不包含快照线程信息。
Thread32Next : 函数 //查询指定进程中当前线程的下一条线程的信息
BOOL WINAPI Thread32Next(
__in HANDLE hSnapshot,
__out LPTHREADENTRY32 lpte
);
OpenThread : 函数 // 负责找出带有匹配的线程ID的线程内核对象 , 对内核对象的使用计数进行递增 , 然后返回对象的句柄 。
HANDLE WINAPI OpenThread(
__in DWORD dwDesiredAccess,
__in BOOL bInheritHandle,
__in DWORD dwThreadId
);
睡眠方式 :
Sleep 函数 : // 该函数可以使线程暂停自己的运行 , 直到 dwMilliseconds 过去为止 。
VOID Sleep( DWORD dwMilliseconds );
转换到另一个线程 :
SwitchToThread 函数 : //当调用这个函数时 , 系统查看是否存在一个迫切需要 CPU 时间的线程 , 如果没有 SwitchToThread 就会立刻返回 FALSE 。如果存在一个迫切需要 CPU 时间的线程 , SwitchToThread 就对该线程进行调用 并返回一个非 0 的值
线程的运行时间 :
GetThreadTimes 函数 : 返回线程得到的 CPU 时间的数量
BOOL GetThreadTimes(
HANDLE hThread ,
PFILETIME pftCreationTime , //获得线程的创建时间
PFILETIME pftExitTime , //获得线程的退出时间
PFILETIME pftKernelTime , //指明线程执行操作系统代码已经经过了多少个 100ns 的 CPU 时间
PFILETIME pftUserTime //指明线程执行应用程序代码已经经过了多少个 100ns 的 CPU 时间
)
GetProcessTimes 函数 : // 返回的时间适用于某个进程中的所有线程,甚至是已经终止运行的线程 。
BOOL GetProcessTimes(
HANDLE hProcess ,
PFILETIME pftCreateTime ,
PFILETIME pftExitTime ,
PFILETIME pftKernelTime ,
PFILETIME pftUser
)
对于高分辨率的配置文件来说 , GetThreadTimes 并不完美 , Windows 确实提供了一些高分辨率性能函数 :
BOOL QueryPerformanceFrequency( LARGE_INTEGER* pliFrequency ) ;
BOOL QueryPerformanceCounter( LARGE_INTEGER* pliCount );
虽然这些函数认为 , 正在执行的线程并没有得到抢占的机会 ,但是高分辨率的配置文件是为短期存在的代码块设置的 。
运用结构环境 :
Windows 实际上运行查看线程内核对象的内部情况 , 以便抓取它当前一组 CPU 寄存器 。
BOOL GetThreadContext( HANDLE hThread , PCONTEXT pContext ) ;
若要调用该函数 , 只需指定一个 CONTEXT 结构 , 对某些标志 (该结构的 ContextFlags 成员) 进行初始化 ,指明想要收回哪些寄存器 。
在调用 GetThreadContext 函数之前 , 应该调用 SuspendThread , 否则线程可能被调度 , 而且线程的环境可能与你收回的不同 。一个线程实际上有两个环境 , 一个是用户方式 , 一个是内核方式 。 GetThreadContext 只能返回线程的用户方式环境 。
////////////获得线程的控制寄存器
CONTEXT Context;
Context.ContextFlags = CONTEXT_CONTROL ;
GetThreadContext(hThread,&Context);
Context.ContextFlags = CONTEXT_FULL ; 可以获得线程的所有重要的寄存器 。这意味着必须编写与 CPU 相关的代码 .
CONTEXT 结构的指令指针 和 堆栈指针 :
-----------------------------------------------------------------------
CPU类型 指令指针 堆栈指针
-----------------------------------------------------------------------
X86 CONTEXT.Eip CONTEXT.Esp
Alpha CONTEXT.Fir CONTEXT.IntSp
SetThreadContext 函数 : //能够修改 CONTEXT 结构中的成员 。将新寄存器值放回线程的内核对象中 。
BOOL SetThreadContext( HANDLE hThread , CONST CONTEXT *pContext ) ;
同样 , 修改其环境的线程应该首先暂停 , 否则其结果将无法预测 。
线程的优先级 :
每个线程都会被赋予一个 0(最低) - 31(最高) 的优先级代号 。如果有 优先级为31 的线程 , 系统会循环优先的分配CPU,
只要有高优先级线程在就不会给低优先级的线程分配 CPU 时间片
对优先级的抽象说明 :
当设计一个应用程序时 , 应该考虑到还有什么应用程序会一道运行 。 然后 , 应该根据你的应用程序中的线程应该具备何种响应性 , 选择一个优先级类 。
Windows 支持 6 个优先级类 : 空闲 , 低于正常 , 正常 , 高于正常 , 高和实时 。
进程是优先级类的一个组成部分 , 你为进程中的线程赋予相对线程优先级 。
程序的优先级 :
当调用 CreateProcess 时 , 可以在 fdwCreate 参数中传递需要的优先级类 。
优先级类的表示类 :
--------------------------------------------------------------------------------
优先级类 标识类
实时 REALTIME_PRIORITY_CLASS
高 HIGH_PRIORITY_CLASS
高于正常 ABOVE_NORMAL_PRIORITY_CLASS
正常 NORMAL_PRIORITY_CLASS
低于正常 BELOW_NORMAL_PRIORITY_CLASS
空闲 IDLE_PRIORITY_CLASS
创建子进程的进程负责选择子进程运行的优先级类 。
SetPriorityClass 函数 : 该函数将 hProcess 标识的优先级类改变为 fdwPriority 参数中设定的值 。
BOOL SetPriorityClass( HANDLE hProcess , DWORD fdwPriority[优先级类的表示类]) ;
GetPriorityClass 函数 : 该函数检索进程的优先级类 。
DWORD GetPriorityClass( HANDLE hProcess );
进程的优先级是由操作系统把进程中所有线程的最高优先级映射给进程的 。
线程相对优先级的标识符常量 :
-------------------------------------------------------------------------------
相对线程优先级 标识符常量
关键时间 THREAD_PRIORITY_TIME_CRITICAL
最高 THREAD_PRIORITY_HIGHEST
高于正常 THREAD_PRIORITY_ABOVE_NORMAL
正常 THREAD_PRIORITY_NORMAL
低于正常 THREAD_PRIORITY_BELOW_NORMAL
最低 THREAD_PRIORITY_LOWEST
空闲 THREAD_PRIORITY_IDLE
SetThreadPriority 函数 : 设置线程的相对优先级 。
BOOL SetThreadPriority( HANDLE hThread , int nPriority[线程相对优先级的标识符常量] );
GetThreadPriority 函数 : 获取线程的相对优先级 。
int GetThreadPriority( HANDLE hThread[线程相对优先级的标识符常量] );
创建一个带有相对优先级为空闲的线程 :
DWORD dwThreadID ;
HANDLE hThread = CreateThread(NULL,0,ThreadFunc,NULL,CREATE-SUSPENDED,&dwThreadID);
SetThreadPriority(hThread,THREAD_PRIORITY_IDLE);
ResumeThread(hThread);
CloseHandle(hThread);
注意,CreateThread 函数创建的新函数带有的相对优先级总是正常优先级。若要使线程以
空闲优先级来运行,应该将 CREATESUSPENDED 标志传递给 CreateThread 函数,这可以防止
线程执行任何代码。然后可以调用 SetThreadPriority,将线程的优先级改为相对空闲优先级。
这时可以调用 ResumeThread,使得线程成为可调度的线程。你不知道线程何时能够获得 CPU 时
间,但是调度程序会考虑这样一个情况,即该线程拥有一个空闲优先级。最后,可以关闭新线
程的句柄,一旦线程终止运行,内核对象就能被撤消。
动态提高线程的优先级等级 :
系统常常需要提高线程的优先等级 , 以便对窗口消息或读取磁盘等IO事件作出反应 。
例如 : 一个线程的优先等级是 13 , 当用户按下一个操作键 , 系统就会将一个 WM_KEYDWON 消息放入线程的队列 。线程队列中有消息 , 该线程就是可调度线程 。 此外 , 键盘设备驱动程序也能够告诉系统暂时提高线程的优先级等级 。 该线程的优先等级可能提高 2 级 , 其当前优先等级改为 15 。系统在优先级为 15 的一个时间片对该线程进行调度 。 一旦该时间片结束 , 系统便将线程的优先级递减 1 , 使下一个时间片的线程优先级将为 14 。 该线程的第三个时间片按优先级等级 13 来执行 。
注意,线程的当前优先级等级决不会低于线程的基本优先级等级。此外,导致线程成为可
调度线程的设备驱动程序可以决定优先级等级提高的数量
系统只能为基本优先级在 1- 15 之间的线程提高其优先级等级 。因为这个范围成为动态优先级范围
//以下两个函数可以使系统的动态提高线程优先级等级的功能不起作用 。
SetProcessPriorityBoost 函数 : //告诉系统激活或停用进行中的所有线程的优先级提高功能
BOOL SetProcessPriorityBoost( HANDLE hProcess , BOOL DisablePriorityBoost );
SetThreadPriorityBoost 函数 : //激活或停用各个线程的优先级提高功能
BOOL SetThreadPriorityBoost( HANDLE hThread , BOOL DisablePriorityBoost );
//以下两个函数可以用来确定是激活还是停用优先级提高功能 :
GetProcessPriorityBoost 函数 :
BOOL GetProcessPriorityBoost( HANDLE hProcess ,PBOOL pDisablePriorityBoost );
GetThreadPriorityBoost 函数 :
BOOL GetThreadPriorityBoost( HANDLE hThread , PBOOL pDisablePriorityBoost ) ;
为前台进程调整调度程序 :
当用户对进程的窗口进行操作时 , 该线程就称为 前台进程 , 所有其他进程则称为后台进程 。Wi n d o w s能够为前台进程中的线程调整其调度算法。会提供比较多的 CPU 时间量
亲缘性 :
GetSystemInfo 函数 : 能够查询机器中的 CPU 数量 。
SetProcessAffinityMask 函数 : 限制在可用 CPU 的子集上运行的单个进程中的线程数量 。
BOOL SetProcessAffinityMask( HANDLE hProcess , DWORD_PTR dwProcessAffinityMask );
hProcess 用于指明要影响的是哪个进程
dwProcessAffinityMask 是个位屏蔽 , 用于指明线程可以在哪些 CPU 上运行 。例如传递 0x00000005 表示该进程中的线程可以在 CPU 0 和 CPU 2 上运行 。不能在 CPU 1 和 CPU 3 至 31 上运行
GetProcessAffinityMask 函数 : 返回进程的亲缘性位屏蔽
BOOL GetProcessAffinityMask( HANDLE hProcess ,
PDWORD_PTR pdwProcessAffinityMask ,
PDWORD_PTR pdwSystemAffinityMask);
SetThreadAffinityMask 函数 : 为各个线程设置亲缘性屏蔽
DWORD_PTR SetThreadAffinityMask( HANDLE hThread , DWORD_PTR dwThreadAffinityMask );
hThread 用于指明要限制哪个线程
dwThreadAffinityMask 用于指明该线程能够在哪个 CPU 上运行 。
SetThreadIdealProcessor 函数 : 为线程设置一个理想的 CPU
DWORD SetThreadIdealProcessor( HANDLE hThread , DWORD dwIdealProcessor );
hThread 指明要为哪个线程设置首先的 CPU 。
dwIdealProcessor 函数不是个位屏蔽而是从 0 到 31 的整数 , 用于指明供线程使用的首先 CPU 。