每一个线程在它的线程内核对象中有一个上下文结构,反映了线程最后一次执行的 CPU 寄存器状态,每隔大约 20ms(可以使用 GetSystemTimeAdjustment 获得,我测得这个间隔大约为 15.6ms),Windows 在所有当前内核对象中查找可调度线程,并选择一个可调度线程,从这个线程的上下文结构中读取 CPU 寄存器状态,这个动作叫做“上下文切换”。这时,这个线程开始执行它的代码,大约 20ms 后,Windows 将 CPU 寄存器的状态保存到这个线程的上下文结构中,这个线程不再执行,Windows 查找下一个可调度线程执行上下文切换,另一个线程继续从它上一次中断的地方开始执行,这个过程从系统开机到系统关闭周而复始。

某些线程不是可调度线程,因为这些线程可能被暂停执行,或者正在待某个任务完成。

由于 WIndows 系统是抢先式操作系统,意味着一个线程可能会在任何时候被其它线程中继,又因为 Windows 系统不是实时操作系统,所以不能保证某一时刻某个线程一定会被调度执行,也不能保证这个线程一定会执行给定的时间片。

线程的暂停和恢复

在线程内核对象内部有一个表示暂停计数器的成员,当你调用 CreateProcess 或 CreateThread 时,这个计数器初始化为 1,避免线程被调度给 CPU,保证线程在完全初始化之前不会执行任何代码,一旦线程完全初始完成,创建函数会检测你是否传递了一个 CREATE_SUSPENDED 标志,如果没有传递这个标志,函数会减少暂停计数器计数,只要计数器为 0,这个线程就成为可调度线程,否则它会暂停执行。

可以使用

DWORD ResumeThread(HANDLE hThread);

来恢复线程的执行,这个函数如果成功,返回之前线程被暂停的次数,否则返回 0xFFFFFFFF。

一个线程可以被暂停多次,那么恢复的话也必须调用 ResumeThread 多次,任何线程都可以调用

DWORD SuspendThread(HANDLE hThread);

来暂停一个线程的执行,只要它有那个线程的句柄,一个线程可以暂停自身的执行,但不可恢复自身。一个线程可以最多被暂停 MAXIMUM_SUSPEND_COUNT (WinNT.h 定义为 127 )次。暂停一个线程时必须非常注意,因为你不知道将要被暂停的线程正在做什么,比如,如果一个线程正在从堆中申请内存,这时,这个线程会在堆中有一个锁,如果这时线程被暂停执行,其它线程会一直待锁被释放,这样会造成死锁。

暂停和恢复一个进程

Windows 并没有提供一个暂停和恢复一个进程内所有线程的函数,我们可以枚举一个进程内的所有线程来达到这个目的,下面是一段摘自 Windows via C/C++ 的一段代码:

VOID SuspendProcess(DWORD dwProcessId, BOOL fSuspend)
{HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessId);if(hSnapshot != INVALID_HANDLE_VALUE){THREADENTRY32 te = {sizeof(THREADENTRY32)};BOOL fOk = Thread32First(hSnapshot, &te);for (; fOk; fOk = Thread32Next(hSnapshot, &te)){// 当前线程属于进程吗?if(te.th32OwnerProcessID == dwProcessId){// 将线程ID转换为一个句柄HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,FALSE, te.th32OwnerProcessID);if(hThread != NULL){// 暂停或恢复这个线程if(fSuspend)SuspendThread(hThread);elseResumeThread(hThread);}CloseHandle(hThread);}}CloseHandle(hSnapshot);}
}

但是这段代码可能不会 100% 有效,如果在调用 CreateToolhelp32Snapshot 后目标进程创建了一个新线程,那么这个线程不会被枚举到,新线程也就不会被暂停;更糟糕的是,在枚举一个线程的过程中一个目标进程的线程可能被释放另一个线程正在被创建,新线程的 ID 可能与释放的线程 ID 相同但可能不属于同一个进程,这样有可能造成暂停了另一个进程的线程。

睡眠

一个线程可以调用

VOID Sleep(DWORD dwMilliseconds);

来使自己睡眠一段时间,这段时间内线程不可调度。

调用这个函数会使线程自动放弃已分配给它的剩余时间片,睡眠的时间也是一个近似值,如果传递一个 INFINITE 参数,表示线程会一直睡眠,直到进程终止,一般情况下很少这样用,但一个永不唤醒的线程可以保留它的线程栈和线程内核对象,其它线程可以使用它们,如果传递一个 0 参数,它告诉线程放弃剩余的时间片,强制系统调度另一个线程,可是如果没有其它线程可调度的话,这个线程可能会立即被重新调度执行。

CONTEXT 结构

CONTEXT 结构允许系统记住线程的状态,线程下一次得到 CPU 时间的时候它可以继续执行。这个结构在 WinNT.h 中定义,它的每一个成员对应一个 CPU 寄存器。

CONTEXT 结构有几个部分,CONTEXT_CONTROL 包含了 CPU 的控制寄存器,如指令指针、栈指针、标志寄存器、函数返回地址等;CONTEXT_INTEGER 表示 CPU 的整数寄存器;CONTEXT_FLOATING_POINT 表示 CPU 的浮点指针寄存器;CONTEXT_SEGMENTS 表示 CPU 的段寄存器;CONTEXT_DEBUG_RGEISTERS 表示 CPU 的调试寄存器;CONTEXT_EXTENDED_REGISTERS 表示 CPU 的扩展寄存器。

可以使用

BOOL GetThreadContext(HANDLE        hThread,PCONTEXT    pContext);

来获取 CONTEXT 结构,调用这个函数之前,你必须为 CONTEXT 分配内存空间,并且设置 ContextFlags 来确定你期望得到哪些寄存状态,你可以在调用 GetThreadContext 之前调用 SuspendThread 来暂停线程的执行,否则你可能得不到指定点的寄存器值,由于 SuspendThread 函数只会暂停用户模式下代码的执行,不会对内核模式代码有影响,所以可以放心地调用 GetThreadContext,它会在内核模式下继续运行来获得期望的结果。

可以使用

BOOL SetThreadContext(HANDLE      hThread,CONST CONTEXT   *pContext);

来设置寄存器的内容,这是一个将线程搞崩溃的好方法。

线程优先级

Windows 系统有 32 种优先级,分别是 0(最低)到 31(最高)。Windows 是一种抢先式操作系统,意味着高优先级的线程会抢先低优先级的线程的执行,只要有高优先级的线程正在执行,低优先级的线程就不会有机会被调度。

Windows 系统并不直接对线程设置优先级,而是使用进程优先级类和线程相对优先级来设置一个线程的优先级。有多各种方法设置优先级类,可以使用 CreateProcess 创建子进程时传递给 fdwCreate 一个表示优先级类的标识符进行设置,还可以通过

BOOL SetPriorityClass(HANDLE hProcess,DWORD  fdwPriority);

设置指定进程的优先级类,第三种方式是使用 start 命令行并指定的一个表示优先级选项来启动一个进程,还可以在任务管理器中设置一个进程的优先级。有 6 种进程优先级类,分别是

优先级类 标识符
实时 REALTIME_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
高于默认 ABOVE_NORMAL_PRIORITY_CLASS
默认 NROMAL_PRIORITY_CLASS
低于默认 BELOW_NORMAL_PRIORITY_CLASS
空闲 IDLE_PRIORITY_CLASS

不能在 CreateThread 时设置新线程的相对优先级,只能通过

BOOL SetThreadPriority(HANDLE   hThread,int nPriority);

设置一个指定线程的优先级,有 7 个相对优先级,分别是

相对优先级 常量标识符
时间关键 THREAD_PRIORITY_TIME_CRITICAL
最高 THREAD_PRIORITY_HIGHEST
高于默认 THREAD_PRIORITY_ABOVE_NORMAL
默认 THREAD_PRIORITY_NORMAL
低于默认 THREAD_PRIORITY_BELOW_NORMAL
最低 THREAD_PRIORITY_LOWEST
空闲 THREAD_PRIORITY_IDLE

线程优先级的动态提升

通常系统在处理一些 I/O 事件或磁盘读取时会动态提升相应线程的优先级,例如,用户按下一个按键,系统会将一个 WM_KEYDOWN 消息放入线程的消息队列中,键盘驱动程序会告诉系统临时提升线程的优先级来处理这个消息,默认情况提升 2 个级别,在第二个时间片,它的优先级降低 1,第三个时间片降到平常水平。系统仅对 1 到 15 之间的优先级做这种提升,这之间的优先级叫做动态优先级范围,系统不会对高于 15 级以上的线程做动态提升,另外,这个提升是由驱动程序告诉系统的。可以使用

BOOL SetProcessPriorityBoost(HANDLE  hProcess,BOOL   bDisablePriorityBoost);
BOOL SetThreadPriorityBoost(HANDLE  hThread,BOOL    bDisablePriorityBoost);

分别设置是否将相应进程或相应线程进行动态提升,使用

BOOL GetProcessPriorityBoost(HANDLE hProcess,PBOOL  pbDisablePriorityBoost);
BOOL GetThreadPriorityBoost(HANDLE  hThread,PBOOL   pbDisablePriorityBoost);

来获得相应进程或线程是否禁用了动态提升。

另外,如果一个低优先级的线程已准备好执行,可是因为高优先级的线程正在一直执行,这种情况如果持续一段时间,一般是 3 到 4 秒钟,系统会临时将低优先级的线程的优先级提高到 15,并运行两个时间片的时间,然后再将的优先级降到平常水平,这样能保证低优先级的线程能得到执行。

I/O 请求优先级调度

当一个线程正在进行长时间的 I/O 请求时,系统因为慢速的 I/O 操作导致响应不畅,解决这个问题的办法是在开始 I/O 操作之前传递 THREAD_MODE_BACKGROUND_BEGIN 标志给 SetThreadPriority,告诉系统降低当前线程的优先级,在 I/O 完成之后传递 THREAD_MODE_BACKGROUND_END 标志给 SetThreadPriority 将优先级恢复到之前的状态;还可以使用 SetProcessClass 分别传递 PROCESS_MODE_BACKGROUND_BEGIN 和 PROCESS_MODE_BACKGROUND_END 来降低或恢复当前进程中所有线程的优先级,注意,只能对当前进程或线程进行这样的操作,不允许这样改变其它进程和其它线程的优先级。

转载于:https://www.cnblogs.com/Fly-pig/archive/2011/02/11/1947290.html

Windows via C/C++ 学习(15)线程调度、线程优先级和亲缘性相关推荐

  1. java 线程亲缘性_第7章 线程调度、优先级和亲缘性(1)

    7.1线程的挂起和恢复 (1)线程挂起 ①创建时(如CreateProcess.CreateThread),传入CREATE_SUSPENDED标志 ②用SuspendThread挂起线程.这个函数可 ...

  2. 线程的调度、优先级和亲缘性——Windows核心编程学习手札系列之七

    线程的调度.优先级和亲缘性 --Windows核心编程学习手札系列之七 每个线程都拥有一个上下文结构,在线程的内核对象中,记录线程上次运行时该线程的CPU寄存器状态.Windows会每隔20ms左右查 ...

  3. Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)

    7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...

  4. Windows核心编程 第七章 线程的调度、优先级和亲缘性(上)

    第7章 线程的调度.优先级和亲缘性 抢占式操作系统必须使用某种算法来确定哪些线程应该在何时调度和运行多长时间.本章将要介绍Microsoft Windows 98和Windows 2000使用的一些算 ...

  5. java 线程亲缘性_线程的调度、优先级和亲缘性

    每隔20ms左右,Windows要查看当前存在的所有线程内核对象.在这些对象中,只有某些对象被视为可以调度的对象.Windows选择可调度的线程内核对象中的一个,将它加载到CPU的寄存器中,它的值是上 ...

  6. java 线程亲缘性_Windows内核之线程的调度,优先级,亲缘性

    1 调度 Windows不是实时操作系统,它是抢占式多线程操作系统.在如果全部优先级同样的情况下,CPU对线程的调度原则是每隔20m就会切换到下一个线程,依据Context中的IP和SP来接着运行上次 ...

  7. 第七章 线程的调度、优先级和亲缘性(4)

    六.运用结构环境 环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. Windows实际上允许查看线程内核对象的内部情况,以便抓取它当前 ...

  8. [笔记]Windows核心编程《十六》线程栈

    系列文章目录 [笔记]Windows核心编程<一>错误处理.字符编码 [笔记]Windows核心编程<二>内核对象 [笔记]Windows核心编程<三>进程 [笔记 ...

  9. [改善Java代码]线程优先级只使用三个等级

    线程的优先级(priority)决定了线程获得CPU运行的机会,优先级越高获得的运行机会越大,优先级越低获得的机会越小.Java的线程有10个级别(准确的说是11个级别,级别为0的线程是JVM,应用程 ...

最新文章

  1. multisim变压器反馈式_变压器的分类及特点
  2. 2.PyCharm安装和使用之HelloWorld
  3. ASP.NET定时调用WebService 运行后台代码
  4. 【Python】感觉是全网最详细Pandas合并数据集操作总结
  5. 微信号也有加人涨粉规则和限制?
  6. MATLAB加入螺旋相位板调制,螺旋相位板的操作原理和使用手册_维尔克斯光电
  7. STVP烧录出现Verify failed at address 0x1000
  8. 图形界面上的任意形状图形按钮
  9. java求解二元二次方程_二元二次方程的解法
  10. clion:输出中文乱码终极解决方案
  11. Stata | 字符函数
  12. 2022广东省安全员A证第三批(主要负责人)培训试题模拟考试平台操作
  13. 如何将喜马拉雅FM的音频下载下来保存
  14. 技术与经济之六:现代化的陷阱
  15. laya 阿拉丁自定义统计
  16. 电脑PDF阅读+谷歌翻译
  17. matlab红外遥感温度反演,一种地表温度多通道热红外遥感反演方法与流程
  18. Spring Boot集成第三方登录之微信登录
  19. IE8,9,10下table th不显示边框解决方法
  20. A股历史行情数据 API 接口

热门文章

  1. 牛客练习赛36 F-Rabbit的蛋糕 (叉积求面积, 记录前缀)
  2. set和vector
  3. Python生成随机数总结
  4. linux diff命令_Linux diff命令示例
  5. 关于c++中map插入元素的问题
  6. 频域补零上采样_AURIX 学习笔记(12)频域法互相关实现超声测距
  7. x86汇编-1(第一章—第二章)8086处理器基本情况
  8. X265源码解析1-Encode方法
  9. 暑期作息时间表模板_人民日报给孩子的暑假作息时间表,转给家长!
  10. Apache Camel简化SOA实施进程