概述
-上下文切换
大约每个20ms,Windows会查看所有当前存在的线程内核对象。在这些对象中,只有一些被认为是可调度的。Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的值(保存在线程内核对象的CONTEXT结构成员里)载入CPU寄存器。
Windows之所以被称为抢占式多线程操作系统,是因为系统可在任何时刻停止一个线程而另行调度另一个线程。
被挂起的线程,等待某事件发生的线程,没有任务的线程都属于不可调度的。
-线程的挂起和恢复
线程内核对象中有一个值表示线程的挂起计数。
调用CreateProcess或CreateThread时,系统将创建线程内核对象,并把挂起计数初始化为1。线程初始化之后,如CreateProcess或CreateThread传入CREATE_SUSPENDED,维持挂起状态。如没传入,将挂起计数递减为0,线程变成可调度的。
// 成功时,返回线程的前一个挂起计数
// 失败时,返回0xFFFFFFFF
DWORD ResumeThread(HANDLE hThread);
// 挂起,返回线程之前的挂起计数
DWORD SuspendThread(HANDLE hThread);
// 一个线程最多可挂起 MAXIMUM_SUSPEND_COUNT
实际开发中,调用SuspendThread时必须小心,因为,试图挂起一个线程时,不知道线程在做什么。如线程正在分配堆中的内存,线程将锁定堆。当其它线程要访问堆的时候,它们的执行将被中止,直到第一个线程恢复。
-进程的挂起和恢复
Windows中不存在挂起和恢复进程的概念,系统不会给进程调度CPU时间。
// 使用此函数时,应考虑到 目标进程会不会在此函数执行过程中,发生线程的创建,结束等行为
VOID SuspendProcess(DWORD dwProcessID, BOOL fSuspend)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessID);
if(hSnapshot != INVALID_HANDLE_VALUE)
{
THREADENTRY32 te = { sizeof(te) };
BOOL fOK = Thread32First(hSnapshot, &te);
for(; fOK; fOK = Thread32Next(hSnapshot, &te))
{
if(te.th32OwnerProcessID == dwProcessID)
{
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
if(hThread != NULL)
{
if(fSuspend)
SuspendThread(hThread);
else
ResumeThread(hThread);
}
CloseHandle(hThread);
}
}
CloseHandle(hSnapshot);
}
}
HANDLE OpenThread(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwThreadID
);
-睡眠
// 告诉系统,在一段时间内自己不需要调度了
VOID Sleep(DWORD dwMilliseconds);
1.将使线程自愿放弃属于它的时间片中剩下的部分。
2.系统设置线程不可调度的时间只是“近似于”所设定的毫秒数。
3.传入INFINITE,告诉系统永远不要调度此线程。
4.传入0,告诉系统,主调线程放弃了时间片的剩余部分。
-线程执行时间
ULONGLONG qwStartTime = GetTickCount64();
...
// 统计两个时间点的时间差。
// 如中间过程线程被中断,则,非执行时间也计入统计结果。
ULONGLONG qwElapsedTime = GetTickCount64() - qwStartTime;
BOOL GetThreadTimes(
HANDLE hThread,
// 100ns为单位。1601.1.1.子夜
PFILETIME pftCreationTime,
// 100ns为单位。1601.1.1.子夜
PFILETIME pftExitTime,
// 100ns为单位。执行内核代码时间。
PFILETIME pftKernelTime,
// 100ns为单位。执行用户代码时间。
PFILETIME pftUserTime
);
BOOL GetProcessTimes(
HANDLE hProcess,
// 100ns为单位。1601.1.1.子夜
PFILETIME pftCreationTime,
// 100ns为单位。1601.1.1.子夜
PFILETIME pftExitTime,
// 100ns为单位。执行内核代码时间。
PFILETIME pftKernelTime,
// 100ns为单位。执行用户代码时间。
PFILETIME pftUserTime
);
-系统为线程分配CPU时间的几种方式
1.依赖约10-15ms的间隔时钟计时器。
2.依赖处理器的64位时间戳计时器,它计算的是机器启动以来的时钟周期数。
当线程被调度程序暂停时,将计算此时TSC值与线程开始执行时,TSC值的差值,并在线程执行时间上加上这个差值。
// 返回给定线程所用的时钟周期数
ULONGLONG QueryThreadCycleTime(HANDLE hThread);
// 返回给定进程所用的时钟周期数
ULONGLONG QueryProcessCycleTime(HANDLE hProcess);
// 获得当前TSC值
__int64 ReadTimeStampCounter();
依据时钟周期差值来计算时间结果本身无法保证,正确性。因为相同的时钟周期数时,
不同的频率对应不同的时间。而处理器的频率是可能会发生变化的。
// 1秒内的性能计数值数
BOOL QueryPerformanceFrequency(LARGE_INTEGER* pliFrequency);
// 获取当前性能计数值
BOOL QueryPerformanceCounter(LARGE_INTEGER* pliCount);
-在实际上下文中谈CONTEXT
组成部分:
1.CPU的控制寄存器,如指令指针,栈指针,标志,函数返回地址。
2.CPU的整数寄存器。
3.CPU的段寄存器。
4.CPU的调试寄存器。
5.CPU的扩展寄存器。
6.查看线程内核对象内部
// 1.SuspendThread挂起线程。
// 2.获取用户模式线程上下文
BOOL GetThreadContext(
HANDLE hThread,
PCONTEXT pContext
);
// 3.代码设置用户模式线程上下文
BOOL SetThreadContext(
HANDLE hThread,
CONST CONTEXT *pContext
);
一个线程实际有两个上下文:用户模式和内核模式。
-线程优先级
1.调度时,选择 优先级高的可调度线程调度。
2.任何时刻,系统中大多数线程都是不可调度的。
如线程调用GetMessage,而系统并没有消息等待处理,则,暂停此线程,取消这个线程当前时间片的剩余时间,再次进入调度。
3.较高优先级的线程总是会抢占较低优先级的线程。
如较低优先级线程1正在执行,此时,较高优先级线程2变成可调度的,系统立即暂停线程1,将CPU分配给线程2。线程2获得一个完整时间片。
4.系统启动时,创建一个页面清零线程。优先级为0。
-进程优先级类
1.
real-time
REALTIME_PRIORITY_CLASS 进程须具备:Increase Scheduling Priority特权。
2.
high
HIGH_PRIORITY_CLASS 例:任务管理器
3.
above normal
ABOVE_NORMAL_PRIORITY_CLASS
4.
normal
NORMAL_PRIORITY_CLASS
5.
below normal
BELOW_NORMAL_PRIORITY_CLASS
6.
idle
IDLE_PRIORITY_CLASS 例:屏幕保护,后台处理
7.指定
7.1.可在CreateProcess的fdwCreate中指定。
7.2.
BOOL SetPriorityClass(
HANDLE hProcess,
DWORD fdwPriority
);
// 获得进程优先级
DWORD GetPriorityClass(HANDLE hProcess);
-相对线程优先级
1.
time-critical
THREAD_PRIORITY_TIME_CRITICAL
2.
highest
THREAD_PRIORITY_HIGHEST
3.
above normal
THREAD_PRIORITY_ABOVE_NORMAL
4.
normal
THREAD_PRIORITY_NORMAL
5.
below normal
THREAD_PRIORITY_BELOW_NORMAL
6.
lowest
THREAD_PRIORITY_LOWEST
7.
idle
THREAD_PRIORITY_IDLE
进程优先级类+相对线程优先级—》优先级值。
// 设置线程的相对优先级
// 改变优先级前,先挂起线程,设置后,再恢复
BOOL SetThreadPriority(
HANDLE hThread,
int nPriority
);
// 获取线程的相对优先级
int GetThreadPriority(
HANDLE hThread
);
-动态提升线程优先级
线程优先级类+线程相对优先级–》线程优先级值:基本优先级值。
线程的当前优先级不会低于线程的基本优先级。
系统只提升优先级值在1-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
);
-为前台进程微调调度程序
如用户需使用某进程的窗口,这个进程称为前台进程。
为改进前台进程的响应性,windows会为前台进程中的线程微调调度算法。(微调只在前台进程优先级类为normal时,进行。)
-调度I/O请求优先级
1.线程会产生I/O请求。
2.低优先级线程产生大量I/O请求,在I/O请求未完成时,可能会挂起高优先级线程。
3.现在,线程可以在进行I/O请求时设置优先级了。
// SetThreadPriority传入THREAD_MODE_BACKGROUND_BEGIN,告诉Windows,线程应发送低优先级的I/O请求。
// SetThreadPriority传入THREAD_MODE_BACKGROUND_END,让线程进行normal优先级的I/O请求。
// 设置I/O优先级时,只允许线程设置自己的。所以,传入的必须是主调线程的句柄。
// 对进程中所有线程使用
// SetPriorityClass传入PROCESS_MODE_BACKGROUND_BEGIN
// SetPriorityClass传入PROCESS_MODE_BACKGROUND_END。
// 设置进程I/O优先级时,也只允许进程设置自己的。所以,传入的必须是主调进程的句柄。
// 对某个文件指定优先级。此时,对此文件,以这里指定的为准。
FILE_IO_PRIORITY_HINT_INFO phi;
phi.PriorityHint = IoPriorityHintLow;
SetFileInformationByHandle(
hFile,
FileIoPriorityHintInfo,
&phi,
sizeof(PriorityHint)
);
-关联性
1.软关联
给线程分配处理器时,如果其它因素一样,系统将使线程在上次运行的处理器上运行。
2.NUMA:非同一内存访问的计算机体系结构
结构的计算机由多个系统板组成,每个系统板都有自己的CPU和内存板块。
此体系结构下,系统在CPU只访问自己所在的系统板的内存时,可达到最佳性能。
为支持这种体系结构,允许我们设置进程和线程的关联性。
硬关联。
// 1.调用GetSystemInfo来查询机器上CPU的数量。
// 2.设置进程中线程可使用的CPU子集
BOOL SetProcessAffinityMask(
HANDLE hProcess,
DWORD_PTR dwProcessAffinityMask
);
DWORD_PTR SetThreadAffinityMask(
HANDLE hThread,
// 须是所在进程的关联性掩码的真子集
DWORD_PTR dwThreadAffinityMask
);
// 子进程将继承进程的关联性
// 也设置作业对象的关联性
// 返回关联性
BOOL GetProcessAffinityMask(
HANDLE hProcess,
PDWORD_PTR pdwProcessAffinityMask,
PDWORD_PTR pdwSystemAffinityMask
);
// 指定一个理想型CPU。索引,非掩码。
DWORD SetThreadIdealProcessor(
HANDLE hThread,
DWORD dwIdealProcessor
);
最后
以上就是舒心雨为你收集整理的Windows核心编程--线程调度/优先级/关联性的全部内容,希望文章能够帮你解决Windows核心编程--线程调度/优先级/关联性所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复