Loading... # Windows核心编程-学习笔记5 ## 线程调度、优先级和亲和性 ### 线程调度 上下文切换:Windows从可调度的线程内核对象中选一个,并将上次保存在线程上下文中的值载入CPU寄存器 线程获得执行代码的权限后,会在进程的地址空间中操作数据。又过了20毫秒,Windows将CPU寄存器存回线程上下文,线程不再运行。系统再次检查剩下的可调度线程内核对象,选择另一个线程的内核对象,将该线程的上下文载入CPU寄存器,然后继续。这样的操作不断重复直至系统关闭 Windows被称为抢占式多线程操作系统,因为系统可以在任何时刻停止一个线程而调度另一个线程 **线程的挂起和恢复**: 线程完全初始化好后,CreateProcess或CreateThread检查是否传递了CREATE_SUSPENDED标志,如果是,函数返回并让新线程保持挂起状态;如果不是,函数将线程挂起计数递减为0,此时线程可调度 恢复挂起线程可以用ResumeThread实现,成功则返回线程的上一个挂起计数,否则返回0xFFFFFFFF 挂起线程可以用SuspendThread实现,同样返回线程的上一个挂起计数 **睡眠**: ~~~c VOID Sleep(DWORD dwMilliseconds) ~~~ * 调用该函数,将使线程自愿放弃属于他的时间片中剩下的部分 * 系统设置线程不可调度时间只是近似值 * 传入INFINITE告诉系统永远不要调度这个进程 * 传入0告诉系统调用线程放弃时间片中的剩余部分,并强制系统调度其他线程;但如果没有相同或较高优先级的可调度线程,就可能重新调度调用了Sleep的线程 **切换到另一个线程**: ~~~c BOOL SwitchToThread(); ~~~ 调用这个函数时,系统查看是否存在急需CPU时间的饥饿线程。如果没有立即返回;如果存在将调度该线程,饥饿线程可以运行一个时间量,然后系统调度程序恢复正常运行 通过该函数,需要某个资源的线程可以强制一个可能拥有该资源的低优先级线程放弃资源;如果在调用SwitchToThread时没有其他线程可以运行,函数返回FALSE,否则返回一个非0值 **线程的执行时间**: 不能使用GetTicketCount64(),因为在Windows中我们不知道线程什么时候会获得CPU时间,因此要使用GetThreadTimes ~~~c++ BOOL GetThreadTimes( [in] HANDLE hThread, [out] LPFILETIME lpCreationTime, [out] LPFILETIME lpExitTime, [out] LPFILETIME lpKernelTime, [out] LPFILETIME lpUserTime ); ~~~ 该函数返回四个时间: * 创建时间:以100ns为单位,从格林尼治时间1601.1.1子夜开始的一个用来表示线程创建时间的绝对值 * 退出时间:同上;若线程仍在运行,则该退出时间处于未定义状态 * 内核时间:一个用来表示线程执行内核模式下的操作系统代码所用时间的相对值,以100ns为单位 * 用户时间:一个用来表示线程执行应用代码所用时间的相对值,以100ns为单位 内核+用户时间为总共时间 GetProcessTimes返回的时间适用于一个指定进程中的所有线程(即使线程已终止) **获取上下文Context**: 使用GetThreadContext,需要先分配一个CONTEXT结构,初始化里面的ContextFlags成员,该成员的作用是说明应检索哪些寄存器 ~~~c++ BOOL GetThreadContext(HANDLE hThread, PCONTEXT pContext); ~~~ 同样也可以调用SetThreadContext来更改结构中的成员,并将新的寄存器值放回到线程内核对象中 ~~~c++ BOOL SetThreadContext(HANDLE hThread, CONST CONTEXT *pContext); ~~~ ### 线程优先级 在调度程序为另一个可调度线程分配CPU前,CPU可以运行一个线程大约20ms,这是所有线程的优先级都相同的情况。但实际上,不同线程不同优先级。 每个线程被赋予0~31的优先级,系统在确定为哪个线程分配CPU时,首先查看优先级31的线程,并以轮询方式调度。只要有优先级为31的线程可供调度,系统就不会为0~30的线程分配CPU,这种情况称为**饥饿**:较高优先级线程占用CPU时间,造成较低优先级线程无法运行。多处理器出现饥饿情况小得多,因为系统总是保持各CPU处于忙碌状态 **优先级编程**: 优先级表示符如下 | 优先级 | 标识符 | | ------------ | --------------------------- | | real-time | REALTIME_PRIORITY_CLASS | | high | HIGH_PRIORITY_CLASS | | above normal | ABOVE_NORMAL_PRIORITY_CLASS | | normal | NORMAL_PRIORITY_CLASS | | below normal | BELOW_NORMAL_PRIORITY_CLASS | | idle | IDLE_PRIORITY_CLASS | 然后进程可以通过调用SetPriorityClass并传入值为上面标识符的fdwPriority来改变自己的优先级 ~~~c++ BOOL SetPriorityClass(HANDLE hProcess, DWORD fdwPriority); ~~~ 同样可以检索进程优先级类,返回上面标识符之一 ~~~c++ DWORD GetPriorityClass(HANDLE hProcess); ~~~ 对于线程优先级设置使用SetThreadPriority ~~~c++ BOOL SetThreadPriority(HANDLE hThread, int nPriority); ~~~ 其中nPriority参数是下面标识符之一 | 相对线程优先级 | 标识符 | | -------------- | ----------------------------- | | time-critical | THREAD_PRIORITY_TIME_CRITICAL | | highest | THREAD_PRIORITY_HIGHEST | | above normal | THREAD_PRIORITY_ABOVE_NORMAL | | normal | THREAD_PRIORITY_NORMAL | | below normal | THREAD_PRIORITY_BELOW_NORMAL | | lowest | THREAD_PRIORITY_LOWEST | | idle | THREAD_PRIORITY_IDLE | 检索线程相对优先级的函数为GetThreadPriority ~~~c++ int GetThreadPriority(HANDLE hThread); ~~~ 注意CreateThread总是创建相对线程优先级为normal的线程;要使线程以idle优先级执行,需要在调用CreateThread时传入CREATE_SUSPENDED标志,这将阻止线程执行任何代码,然后调用SetThreadPriority设置为idle,接着调用ResumeThread,线程就可被调度了 **动态提升线程优先级**: * 动态优先级范围:系统只提升优先级1~15的线程,且不会提升超过15 * SetProcessPriorityBoost允许或禁止系统提升一个进程中所有线程的优先级 * SetThreadPriorityBoost允许或禁止提升某个线程的优先级 * GetProcessPriorityBoost、GetThreadPriorityBoost:判断当前是否启用了优先级提升 另一种造成系统动态提升线程优先级情况:系统检测到一个低优先级线程饥饿3到4秒了,就会将饥饿线程优先级动态提升到15,并允许该线程运行两个时间片;结束后恢复到基本优先级 ### 亲和性 软亲和性:如果其他因素一样,就尝试让线程在上一个运行的处理器中运行,有助于重用仍在处理器高速缓存中的数据 GetSystemInfo获取机器CPU数量,要限制线程在可用CPU的一个子集上运行可以调用SetProcessAffinityMask ~~~c++ BOOL SetProcessAffinityMask(HANDLE hProcess, DWORD_PTR dwProcessAffinityMask); ~~~ 第一个参数表示要设置的进程,第二个参数是一个位掩码,代表线程可以在哪些CPU上运行,例如传入5代表可以在CPU0、2上运行 子进程继承进程亲和性,如果一个进程的亲和性掩码为5,那么它的子进程的所有线程掩码都是5并共用同一组CPU GetProcessAffinityMask可以获取亲和性掩码 ~~~c++ BOOL GetProcessAffinityMask(HANDLE hProcess, PDWORD_PTR pdwProcessAffinityMask, PDWORD_PTR pdwSystemAffinityMask); ~~~ 这里传入一个进程句柄,可以从pdwProcessAffinityMask获取进程的亲和性掩码,可以从pdwSystemAffinityMask获取系统的亲和性掩码,即哪些CPU可以运行进程里的线程。**进程的亲和性掩码总是系统的亲和性掩码的一个真子集** 可以调用SetThreadAffinityMask为单独的线程设置亲和性掩码,返回值是线程之前的亲和性掩码 ~~~c++ DWORD_PTR SetThreadAffinityMask(HANDLE hThread, DWORD_PTR dwThreadAffinityMask); ~~~ 最后修改:2025 年 12 月 18 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏