《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亲缘性和优先级会共同影响线程的调度。

### 三级标题:线程调度优先级的基本机制 线程调度优先级是操作系统调度器决定哪个线程获得CPU资源的重要依据。在多线程环境中,每个线程都有一个优先级,操作系统会根据优先级高低决定线程的执行顺序。高优先级线程一旦进入可执行状态,会抢占低优先级线程的CPU资源,即使低优先级线程正在运行,也会被暂停执行,让位于高优先级线程[^1]。 ### 三级标题:线程优先级的分类与调整 线程优先级可以分为静态优先级动态优先级。静态优先级在创建线程时设定,通常不会随时间变化;而动态优先级则根据系统运行状态调度策略自动调整。在Java中,可以通过`setPriority(int p)`方法设置线程优先级,取值范围为1到10,其中`Thread.MAX_PRIORITY`表示最高优先级,`Thread.MIN_PRIORITY`表示最低优先级[^3]。 在Linux系统中,线程调度策略优先级可以通过`pthread_setschedparam`函数进行设置。例如,可以将线程调度策略设置为`SCHED_FIFO`,并设置最大优先级值,以确保该线程具有更高的执行优先权[^4]。 ### 三级标题:线程调度策略与优先级的交互 Linux系统支持多种调度策略,如`SCHED_FIFO`(先进先出)、`SCHED_RR`(轮转调度`SCHED_OTHER`(默认调度策略)。对于`SCHED_FIFO`策略,线程优先级范围为1到99,属于抢占式调度。在同一优先级下,多个线程按顺序执行,但一旦某个高优先级线程变为可运行状态,它将立即抢占CPU资源,直到它主动让出CPU或被更高优先级线程抢占[^5]。 以下是一个Linux线程设置调度策略优先级的示例代码: ```c void* thread_function(void* arg) { // 获取线程的tid pid_t tid = gettid(); // 定义调度策略优先级变量 int policy; struct sched_param sched_param; // 获取当前线程调度参数 pthread_getschedparam(pthread_self(), &policy, &sched_param); // 打印当前的调度策略优先级 printf("Current thread (tid: %d) policy: %d priority: %d\n", tid, policy, sched_param.sched_priority); // 设置线程调度策略为FIFO policy = SCHED_FIFO; // 设置线程优先级为最大值 sched_param.sched_priority = sched_get_priority_max(policy); // 设置线程调度策略优先级 pthread_setschedparam(pthread_self(), policy, &sched_param); // 再次获取并打印调度参数 pthread_getschedparam(pthread_self(), &policy, &sched_param); printf("New thread (tid: %d) policy: %d priority: %d\n", tid, policy, sched_param.sched_priority); // 线程工作代码... return NULL; } ``` ### 三级标题:线程优先级对系统行为的影响 线程优先级的设定对系统行为有重要影响。高优先级线程可以更快地响应用户或系统事件,但若过度使用高优先级线程,可能导致低优先级线程“饥饿”,即长时间得不到执行机会。因此,在设计多线程应用程序时,应合理分配线程优先级,以确保系统的整体响应公平。 此外,线程优先级还与垃圾回收机制相关。Java的垃圾回收线程通常运行在低优先级的守护线程中,这确保了垃圾回收不会对应用程序的能造成显著影响,但也可能导致在内存压力高时无法立即执行回收操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值