《Windows核心编程》第二部分(线程调度,优先级和亲缘性)

(4)线程调度,优先级和亲缘性

如上所诉,在每个线程内核对象中都有一个CONTEXT结构,其中保存了线程上一次运行寄存器的情况,在线程调度中,windows大约每隔20ms就会从线程内核对象中选出一个可调度的线程,将其CONTEXT还原到CPU寄存器中,如此循环。

我们对操作系统内部的线程调度可以做出的影响很小,无法保证一个线程在某个时间段一直运行。

一般可调度的进程比较少,大多线程都在等待某个其他事件。

windows是非实时调度系统,不能保证某一个线程在某时一定获取CPU时间。

线程内核对象中有一个属性表示挂起计数,CreateThread创建对象后,将此计数设置为1,表示线程挂起,不可调度,初始化后如果传入了CREATE_SUSPEND标识则继续将线程挂起,否则将挂起计数设为0,此时线程就可调度。

也可以在运行时使用SuspendThread函数来挂起线程,然后使用ResumeThread函数来唤醒线程,值得注意的是,可以使用SupendThread多次挂起线程,线程有一个挂起计数器,初始值是0,挂起一次(含初始挂起),计数器就自增1,ResumeThread一次就自减1,当此计数器为0的时候,线程就被唤醒开始从挂起的地方开始执行。

线程暂停的最多次数可以是MAXIMUM_SUSPEND_COUNT次

在实际开发过程中SuspendThread的使用要很小心,我们要明确当前线程挂起之后不会影响到其他线程的运行,不会发生死锁。如果一个线程试图从堆栈上分配内存,那么该线程将在堆栈上设置一个锁,暂停后锁一直存在会影响其他线程的访问。

进程不是调度的基本单位,不能对进程执行挂起和调度的操作,但是可以通过挂起进程中的所有线程来控制进程调度。WaitForDebugEvent函数可以用来挂起。ContinueDebugEvent函数可以用来恢复。

除了使用SuspendThread挂起其他线程,自身也可以使用sleep函数来将自己挂起。可以传入线程睡眠的时间,传入0表示线程放弃本次时间片的剩余部分。如果传入INFINITE会告诉系统永远不要调度此线程,不建议使用,建议直接让线程退出。

SwitchToThread函数,如果存在另一个可调度的线程,则系统让此线程运行,可以让急需调度线程调度,与Sleep(0)相似。只是SwitchToThread还允许调度低优先级线程。如果没有让其他线程运行返回FALSE,不然会返回一个非0值。

GetTickCount64()可以用来计算当前时间。

ULONG start=GetTickCount64();  
  
  //do something.  
  
ULONG end=GetTickCount64();  

但是如果代码在获取第一个时间被中断之后再度获取第二次时间的话此时间就不准确了。

//windows提供一个可以获得一个线程来获得CPU的时间
BOOL GetThreadTime(
//想获得的线程句柄
HANDLE hThread,
//返回创建的时间到1601-1-1 00:00:00的时间,单位是100ms
PFILETIME pftCreationTime,
//返回退出的时间到1601-1-1 00:00:00的时间,单位是100ms
PFILETIME pftExitTime,
//表示线程执行内核模式下的时间
PFILETIME pftKernelTime,
//表示线程执行用户模式下的时间
PFILETIME pftUserTime);

如果要进行高精度的计算时,windows提供了以下函数

// 这两个函数假设正在执行的线程不会被抢占,都是针对生命期很短的代码块
BOOL QueryPerformanceFrequency(LARGE_INTEGER *pliFrequency); 
BOOL  QueryPerformanceCounter(LARGE_INTEGER *pliCount);

CONTEXT结构包含了主机CPUS上每个寄存器的数据结构。获取当前寄存器状态,修改当前寄存器状态。

//获取cpu中CONTEXT结构,获取当前寄存器状态
//分配CONTEXT结构后需要初始化ContextFlag标志
//ContextFlag标志
// CONTEXT_CONTROL表示控制寄存器。
// CONTEXT_INTEGER表示整数寄存器。
// CONTEXT_FLOATING_POINT 表示浮点寄存器。
//CONTEXT_SEGMENTS表示段寄存器。
//CONTEXT_DEBUG_REGISTER表示调试寄存器。
//CONTEXT_EXTENDED_REGISTERS表示扩展寄存器
// CONTEXT_FULL 表示CONTEXT_CONTROL |CONTEXT_INTEGER|CONTEXT_SEGMENTS。
//在调用此函数之前要先调用SuspendThread挂起线程,防止寄存器内容改变。
//一个线程有两个环境。一个是用户方式一个是内核方式,
//GetThreadContext只能获得用户方式并且SupendThread只能暂停用户方式。
//在调用之前要对pContext里的ContextFlags标识进行初始化
//指明想要检索哪些寄存器比如说pContext.ContextFlags=CONTEXT_CONTROL。
BOOL GetThreadContext(HANDLE pThread,PCONTEXT pContext);
//修改CONTEXT结构
BOOL SetThreadContext(HANDLE hThread,CONST CONTEXT *pContext);

线程优先级
windows线程的优先级会影响调度程序的选择,windows的优先级范围是从0到31。优先调度优先级较大的线程。
进程优先级
Windows支持6个进程优先级类:空闲,低于正常,正常,高于正常,高和实时。正常最为常用,为99%的进程使用,可以在进程创建的CreateProcess上设置通过fdwCreate标识符设置优先级。

fdwPriority:
实时        REALTIME_PRIORITY_CLASS
高            HIGH_PRIORITY_CLASS
高于正常     ABOVE_NORMAL_PRIORITY_CLASS
正常          NORMAL_PRIORITY_CLASS
低于正常    BELOW_NORMAL_PRIORITY_CLASS
空闲             IDLE_PRIORITY_CLASS


//设置优先级
BOOL SetPriorityClass(ANDLE hProcess,DWORD fdwPriority);
//获得优先级
DWORD GetPriorityClass(HANDLE hProcess);

Windows支持7个相对线程优先级:空闲,最低,低于正常,正常,高于正常,最高,关键时间,相对于进程优先级。
nPriority可以是以下标识符:

//设置线程优先级
BOOL SetThreadPriority(HANDLE hThread,int nPriority);
//获得线程优先级
int GetThreadPriority(HANDLE hThread);
nPriority:
关键时间       THREAD_PRIORITY_TIME_CRITICAL
最高           THREAD_PRIORITY_HIGHEST
高于正常      THREAD_PRIORITY_ABOVE_NORMAL
正常          THREAD_PRIORITY_NORMAL
低于正常      THREAD_PRIORITY_BELOW_NORMAL
最低            THREAD_PRIORITY_LOWEST
空闲              THREAD_PRIORITY_IDLE

所以说一个线程的优先级是由进程的优先级类和线程的相对线程优先级决定的。
运行后操作优先级

0_1527959337172_baced9dd-a11d-454c-8ee3-4b0ab6dff79d-image.png
0优先级保留提供零页线程使用。17,18,19,20,21,27,28,29,30只有基于内核方式运行的设备驱动程序可以设置此优先级,用户方式不能。实时优先级不能低于16,非实时不能高于15。

系统也会提升暂时提升一个线程的优先级,线程的当前优先级不会低于进程的基本优先级。而且设备驱动程序可以决定动态提升的幅度。系统只提升优先级值在1~15的线程。

//设置禁止提升进程内所有线程的优先级
BOOL SetProcessPriorityBoost(HANDLE hProcess,BOOL bDisablePriorityBoost);
//设置禁止提升线程的优先级
BOOL SetThreadPriorityBoost(HANDLE hThread,BOOL bDisablePriorityBoost);

在系统检测到有饥饿情况出现时,系统会动态提升此线程的优先级。
用户正在使用的窗口被称为前台窗口。这个进程就被称为前台进程。为了改进前台进程的响应性,windows会为前台进程中的线程微调调度算法。使前台进程的线程分配比一般情况下更多的时间片。

线程与CPU的亲缘性
默认情况下,windows在分配cpu时采用软亲缘性的方式。系统使线程在上一次运行的CPU上运行。便于使用上一次的缓存。

GetSysInfo函数可以用来查询CPU的数量

SetProcessAffinityMask可以用来限制某个进程上的线程都在一个CPU上运行,硬亲缘性。

有一种新的计算机结构,NUMA(非统一内存访问),此结构中计算机包含多个插板,每个插板上都有多个CPU和一个内存块,多个CPU共用一个内存。

0_1528096399125_e5bdddf5-5346-481d-ae41-e39733f75936-image.png

//第一个参数代表要设置的进程句柄。第二参数是一个位掩码。代表线程可以在哪些cpu上运行。
BOOL SetProcessAffinityMask( HANDLE hProcess,  DWORD_PTR dwProcessAffinityMask);

子进程将继承父进程的CPU关联性
GetProcessAffinityMask返回进程的关联掩码。
SetThreadAffinityMask设置某个线程只在一组cpu上运行。

//dwIdealProcessor是一个0到31/63之间的整数。表示线程希望设置的cpu。
//MAXIMUM_PROCESSOR值,表示没有理想的cpu
DWORD SetThreadIdealProcessro(HANDLE hThread,DWORD dwIdealProcessor);

线程CPU亲缘性和优先级会共同影响线程的调度。

猜你喜欢

转载自blog.csdn.net/hhouxiang/article/details/80664675