第七章:线程调度、优先级、和关联性

1. 上下文切换:大约每隔20ms(具体时间可以使用GetSystemTimeAdjustment函数的第二个参数来查看),windows都会查看所有当前存在的线程内核对象.在这些对象中,只有一些被认为是可调度的.windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的值载入CPU寄存器.
需要注意的是:我们无法保证线程总是在运行,线程会获得整个处理器,系统将不允许运行其他线程等.

2. 在线程内核对象中存在一个线程挂起计数,调用CreateThread或GetProcess时,系统将创建线程内核对象,并把挂起计数初始化为1.这样,就不会给这个线程调度CPU了.因为此时我们需要等线程完全初始化好才行.等待初始化成功之后,将会检查CreateThread和CreateProcess是否有CREATE_SUSPENDED标志传入.如果有,函数会返回并让新的线程处于挂起状态,否则,函数将使挂起计数减为0,使其成为可调度状态,除非它要等待某个时间的发生(例如键盘的输入,其他被锁住的变量或资源等).

另外,一个线程可以允许挂起多次,假设某个线程已经挂起了N次,如果想让他继续分配CPU之前必须恢复N次(即调用ResumeThread函数,并且此时N应该小于127).

需要注意的是ResumeThread和SuspendThread两个函数如果成功调用,则返回线程之前的挂起计数.而且就内核模式下运行情况而言SuspendThread是异步的,但在线程恢复之前,它是无法在用户模式下运行的.(也就是说,他只是在内核模式下异步)

Windows中不存在挂起和恢复进程的概念.因为系统从来不会给进程调度CPU时间.在一个特殊情况下,即调试器处理WaitForDebugEvent返回的调试事件时,windows将冻结被调试进程中的所有线程,直至调试器调用ContinueDebugEvent.

void SuspendProcess( DWORD dwProcessID,BOOL fSuspend )

{

HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD,dwProcessID );//获取由dwProcessID标示的句柄

if( hSnapshot )

{

THREADENTRY32 te = { sizeof( te ) };

BOOL fOk = Thread32First( hSnapshot,&te ); //查找进程的第一个线程 for( ;fOk = Thread32Next( hSnapshot,&te ) )

{

if( te.th32OwnerProcessID == dwProcessID )

{

HANDLE hThread = OpenThread( THREAD_SUSPEND_RESUME,FALSE,te.th32ThreadID );

//匹配线程ID相关联的内核对象,需要注意的是,此时内核计数会递增1

if( hThread != NULL )

{

if( fSuspend )

{

SuspendThread( hThread );

}

else

{

ResumeThread( hThread );

}

}

CloseHandle( hThread );

}

}

CloseHandle( hSnapshot );

}

}

因为在使用SuspendProcess函数时,其参数dwProcessID标示的进程未定(是否已经注销,或者是否产生了新的线程和住校的新的线程等)的条件存在,所以我们最好少使用它来挂起进程.

3. 睡眠一个线程可以调用Sleep函数:

VOID Sleep(DWORD dwMilliseconds)//使得线程自己挂起dwMilliseconds时间
调用干函数需要注意的有:

◆ 调用Sleep函数,将使线程自愿放弃属于自己的剩余时间片

◆ 系统设置线程不可调度的时间只能是"大概"的毫秒数.因为windows非实时系统

◆ 可以传入INFINITE,此时告诉系统,永远不要调度这个进程.

◆ 可以传入0,此时告诉系统,主调线程放弃了时间片的剩余部分.他强制系统调用其他线程.但是系统有可能调度刚刚调度了Sleep的那个线程.

4. 切换线程:

BOOL SwitchToThread();

备注:调用这个函数时,系统查看是否正在急需CPU时间的饥饿线程.如果没有,则函数会立即返回.如果存在,则函数将调度改线程(其优先级可能比函数的主调线程优先级低).饥饿线程可以运行一个时间量,然后系统调度程序恢复正常.

调用SwitchToThread与调用Sleep类似,传入0即可.区别在于,SwitchToThread允许执行低优先级的线程.而Sleep会立即重新调度,即使低优先级线程还处于饥饿状态.

5. 如果我们想要得到某个复杂算法所耗费的时间.我们可以调用GetThreadTimes

BOOL GetThreadTimes(

HANDLE hThread,

PFILETIME pftCreationTime,

PFILETIME pftExitTime,

PFILETIME pftKernelTime,

PFILETIME pftUserTime);

这四个返回值分别定义为如下表所示:

时间值

含义

创建时间

一个用来表示线程创建时间的绝对值,用100ns为单位,从格林时间1606年1月1号子夜开始计算创建的时间

退出时间

用100ns为单位表示,从格林时间1606年1月1号子夜开始计算创建的时间,一个用来表示线程退出时间的绝对值,如果线程仍然在运行,退出时间是没意义的

内核时间

一个用来表示线程执行内核模式下的操作系统代码所用时间的绝对值,用100ns为单位表示

用户时间

用来表示线程执行应用程序代码所使用的时间绝对值,用100ns为单位表示

下面这个函数可以把PFILETIME表示的时间转化为我们实际使用的时间:

__int64 FileTimeToQuadWord( PFILETIME pft )

{

return (Int64ShllMod32(pft->dwHighDateTime,32) | pft->dwLowDateTime);

}

另外还有一个获取进程的运行时间:

BOOL GetProcessTimes(

HANDLE hThread,

PFILETIME pftCreationTime,

PFILETIME pftExitTime,

PFILETIME pftKernelTime,

PFILETIME pftUserTime);

此时获取到的时间显然是所有线程时间的总和.

Vista系统的特性:

在为线程分配CPU时间的方式发生了变化.系统不再依赖于间隔时钟计时器(大约10~15ms间隔时间),而是改用了处理器的64位时间戳计时器(TSC),它计算的是机器启动以来的时钟周期数.可以使用ReadTimeStampCounter来获取TSC的值.这个宏被定义为C++编译器提供的内部函数__rdtsc.

QueryThreadCycleTime和QueryProcessCycleTime函数分别返回给定线程或者进程的所有线程所用的时钟周期数.

如果想要使用高精度性能分析,必须使用以下两个函数:

BOOL QueryPerformanceFrequency(LARGE_INTEGER* pliFrequency);

BOOL QueryPerformanceCounter(LARGE_INTEGER* pliCount );

这些函数假设正在执行的线程不会被抢占.

可以使用<windows核心编程>上作者封装好的一个类.

6. CONTEXT结构记载了线程的状态,这样线程在下一次获取CPU可以运行时,就可以从上次停止处继续.

Windows允许我们查看线程内核对象的内部结构成员.可以使用GetThreadContext:

BOOL GetThreadContext(

HANDLE hThread,

PCONTEXT pContext);

函数调用情况说明:首先分配一个CONTEXT结构,初始化一些标志(结构ContextFlags成员)以表示获取那些寄存器.然后将pContext作为返回值.

需要注意的是,在调用这个函数时,应先调用SuspendThread;否则系统可能正好获得调度此线程;这样一来线程的上下文与所获得的信息就不一致了.

上下文分为:用户模式和内核模式.GetThreadContext只能返回用户模式.

ContextFlags和取值对应的关系:

ContextFlags取值

获取的意义

CONTEXT_CONTROL

CPU控制寄存器

CONTEXT_INTEGER

整数寄存器

CONTEXT_SEGMENTS

段寄存器

也可以利用SetThreadContext来设置上下文.但是一般不提倡使用.以为这样很容易就造成访问违规.

7. 较高优先级的线程总是会抢占较低优先级的线程,无论较低优先级的线程是否正在执行.另外,系统启动时,系统将创建一个名为页面清零线程的特殊线程.这个线程的优先级定为0,而且是整个系统中唯一一个优先级为0的线程.页面清零线程负责在没有其他进程需要执行的时候,将系统内存中的所有闲置页面清零.
■ 进程优先集类

优先级

描述

Real-time(实时)

此进程中的线程必须立即响应事件,执行实时任务.此进程中的线程还会抢占OS的组件的CPU时间.因此需要极为小心

High(高)

此进程中的线程必须立即响应事件,执行实时任务.任务管理器运行在这一级,因此用户可以通过它结束是空的进程

Above normal (高于标准)

此进程中的线程运行在normal和high优先级之间

Normal(标准)

此进程中的线程无需特殊的调度

Below normal (低于标准)

此进程中的线程运行在normal和idle之间

Idle(低)

此进程中的线程在系统空闲时运行,屏幕保护程序,后台使用程序和统计数据收集软件通常使用该进程.

进程不能运行在real-time优先级类的.除非用户有Increase Scheduling Priority(提高计划优先级)特权.默认情况下,隶属于管理员或者高级用户组的用户都具有这一权限.

■ 相对线程线程优先级

相对线程优先级

描述

Time-critical

对于real-time优先级类,线程运行在31上,所有其他优先级运行在15

Highest

线程运行在高于normal之上两个级别

Above normal

线程运行在高于normal之上一个级别

Normal

线程运行在normal级别上

Below normal

线程运行在低于normal之下一个级别

Lowest

线程运行在低于normal之下两个级别

Idle

对于real-time优先级类,线程运行在16;所有其他优先级运行在1.

线程都属于某个优先级类,可以指定进程中线程的相对线程优先级.请记住:线程优先级是相对于进程优先级的.如果改变进程优先级.线程的相对优先级不变,但是优先级值将变化.如果是运行在用户模式的应用程序,那么无法获取一下优先级:17~19,20,21,27~30.如果是内核模式,则没有限制.real-time优先级类的线程,其优先级值不能低于16,而飞real-time不能高于15.

一般而言,在编程时,较高优先级的线程大多数时候都应是不可调度的.而优先级低的保持为可调度状态.(因为这样就可以使得系统执行高优先级的线程时很快就能得到CPU时间)

8. ■ 如果想要指派某个优先级给进程,我们可以再调用GetProcess时,向fdwCreate参数传入以下优先级标示符.

优先级

标识符

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

可以使用以下两个函数来设置和获取某个进程的优先级:

BOOL SetPriorityClass(

HANLE hProcess,

DWORD fdwPriority)

DWORD GetPriorityClass

HANLDE hProcess)

通过命令行界面调用程序时,程序的起始优先级是normal.但是使用START命令就可以指定某个优先级. 例如C:\>START /LOW process.exe

同理,有线程的相对优先级标识符

相对线程优先级

符号常量

Time-critical

THREAD_PRIORITY_TIME_CRITICAL

Highest

THREAD_PRIORITY_TIME_HIGHEST

Above normal

THREAD_PRIORITY_TIME__ABOVE_NORMAL

Normal

THREAD_PRIORITY_TIME_NORMAL

Below normal

THREAD_PRIORITY_TIME_BELOW_NORMAL

Lowest

THREAD_PRIORITY_TIME_LOWEST

Idle

THREAD_PRIORITY_TIME_IDLE

相应的,可以使用以下函数来获取线程的相对优先级:

Int GetThreadPriority(HANDLE hTHread);

说明:windows并没有提供返回线程优先级的函数,这一遗漏是Microsoft故意的.请记住Microsoft保留了任何时候改变调度算法的权利.我们设计的应用程序不应该了解调度算法具体的细节.如果我们始终使用进程优先级类和相对线程优先级类,那么我们的应用程序在现在和在OS未来版本都可以运行得很好.

■ 系统通过线程的相对优先级加上线程所属进程的优先级来确定线程优先级值(即基本优先级值),然而系统会因为某类特殊事件来提升他的基本优先级(例如:IO事件).

系统提升的优先级在随着进程的运行会越来越小,但是不会低于原理啊的线程的基本优先级.而且只能提升1~15的线程(这个范围也叫着动态优先级范围),而且也不会超过这个范围(超过15也叫实时范围).系统不能动态提升实时范围内的线程.

当然也可以禁止/允许系统对某个线程优先级进行动态提升:

BOOL SetProcessPriorityBoot(

HANDLE hProcess,

BOOL  bDisablePriorityBoot);

BOOL SetThreadPriorityBoot(

HANDLE hThread,

BOOL hDisablePriorityBoot);

相对应的获取优先级权限:

BOOL GetProcessPriorityBoot(

HANDLE hProcess,

PBOOL  bDisablePriorityBoot);

BOOL GetThreadPriorityBoot(

HNALDE hThread,

PBOOL bDisablePriorityBoot);

系统提升优先级的另一种途径是,线程饥饿.

如果用户需要使用某个进程的窗口,这个进程成为前台进程.这时系统会微调调度算法,使得该进程具有更多的时间片.需要注意的是,这种微调的前提是该进程是normal优先级时,方才进行.在Vista中,其实是可以设置是否进行微调操作的.

■ 设置线程优先级将影响系统如何给线程分配CPU资源.但是,除了线程优先级还存在I/O请求,以对磁盘文件读写数据.

从vista开始,线程可以在进行I/O请求时设置优先级了.通过调用SetThreadPriority并传入THREAD_MODE_BACKGROUND_BEGIN来告知windows,线程应该发送低优先级的IO请求(这也同时会降低线程的CPU调度优先级).或者传入THREAD_MODE_BACKGROUND_END让线程进行normal优先级IO请求(以及normal CPU调度优先级).

如果让进程中的所有线程都进行低优先级的IO请求和低CPU调度,那么我们可以调度SetPriorityClass,传入PROCESS_MODE_BACKGROUND_BEGIN标志,相反的操作可以传入PROCESS_MODE_BACKGROUND_END标志.

另外,如果想让系统在更细粒度使用,normal优先级线程还可执行对某个文件执行后台优先级IO:

SetFileInformationByHandle设置优先级将覆盖进程的优先级或者线程(即通过上述两个函数设置的优先级).

9. 概念:

软关联:如果其他因素都一样,系统将使线程在上一次运行的处理器上运行.让线程始终在同一个处理器上运行,有助于重用仍在处理器高速缓冲中的数据.

硬交换:控制CPU让哪些CPU运行特定的线程.

使用GetSystemInfo来查询机器上CPU的数量.

限制某些线程只在可用CPU的子集上运行:

BOOL SetProcessAffinityMask(

HANDLE hProcess,

DWORD_PTR dwProcessAffinityMask);//位掩码.

请注意:子进程将继承进程关联性.

相应的,获取进程的关联性掩码:

BOOL GetProcessAffinityMask(

HANDLE hProcess,

PDWORD_PTR pdwProcessAffinityMask,//返回进程的关联性掩码

PDWORD_PTR pdwSystemAffinityMask)//返回系统关联性掩码

所谓关联性掩码,即表述系统中哪个CPU可以永兴这些线程,进程的关联性掩码总是系统相关联行掩码的一个真子集.

可以通过使用下列函数来获取关联性掩码:

DWORD_PTR SetThreadAffinityMask(

HANDLE hThread,

DWORD_PTR dwThreadAffinifyMask);//线程可以再哪些CPU上运行.其必 //须是进程关联性掩码的真子集.

返回值:线程之前的关联性掩码.

因为如果进行上述操作,可能会造成分配资源的极度不可理,因此windows提供了另外一个方法来给线程设置一个理想的CPU.

DWORD  SetThreadIdealProcessor(

HANDLE hThread,

DWORD dwIdealProcessor)//取值为0~31/63,如果传递MAXIMUM_PROCESSORS表述 //线程没有理想的CPU.

返回值:成功返回理想的CPU,否则返回MAXIMUM_PROCESSORS.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值