概述
线程的挂起和恢复
线程内核中各有一个值表示线程的挂起计数。调用CreateProcess或者CreateThread时,系统将创建线程内核对象,并把挂起计数初始化为1。这样便不会给这个线程调度CPU了。
在线程初始化后,CreateProcess或者CreateThread函数将查看是否有CREATE_SUSPENDED标志传入。如果有,则函数返回,新线程立刻挂起。如果没有,函数会将线程的挂起计数递减为0,这时新线程变为可调度的,除非它还在等待某个事件发生(例如键盘输入等)。
当我们创建了一个处于挂起状态的线程后,我们可以使用ResumeThread函数:DWORD ResumeThread(HANDLE hThread);
如果函数调用成功,其返回线程前一个挂起计数。否则,它将返回0xffffffff。
一个线程可以被挂起多次,相应也要恢复多次。除了在创建线程时使用CREATE_SUSPENDED标志外,还可以通过调用SuspendThread来挂起线程。
DWORD SuspendThread(HANDLE hThread);
任何线程都可以调用这个函数挂起另一个线程(只要有线程句柄)。显然,线程可以挂起自己,但是无法自己恢复。SuspendThread返回的也是之前的挂起计数。一个线程最多可以挂起MAXIMUM_SUSPEND_COUNT(127)次。
睡眠
线程还可以告诉系统,在一段时间内自己不需要调度了。这可以通过调用Sleep实现:
VOID Sleep(DWORD dwMilliseconds);
调用此函数意味着:1.线程自愿放弃属于它的时间片中剩下的部分。
2.如果传入INFINITE,则是告诉系统,永远不要调度这个线程。
3.传入0,则是告诉系统,放弃当前cpu时间片的剩余时间。
## 线程执行时间 ##
可以使用GetThreadTimes函数,
BOOL GetThreadTimes(
HANDLE hThread,
PFILETIME pftCreationTime,
PFILETIME pftExitTime,
PFILETIME pftKernelTime,
PFILETIME pftUserTime);
这个函数返回四个不同的时间,创建时间:用100ns为单位,表示线程创建的绝对时间。
退出时间:用100ns为单位,表示线程退出的绝对时间。
内核时间:用100ns为单位,表示线程执行内核模式下的操作系统代码所用时间。
用户时间:用100ns为单位,表示线程执行应用代码所用时间的绝对值。
使用例子:P172
GetProcessTimes函数和GetThreadTimes函数类似,使用GetProcessTimes所得到的时间是一个进程中所有线程的时间。例如:返回的内核时间是所有线程在内核模式下所耗时间的总和。
详解CONTEXT结构
CONTEXT结构中的每一个数据成员都在cpu上能找到一个与之相对应的寄存器。在x86机上,数据成员是Eax,Ebx,Ecx,Edx等。
CONTEXT结构包括CONTEXT_CONTROL(包含CPU的控制寄存器),CONTEXT_INTEGER(标识CPU的整数寄存器),CONTEXT_SEGMENTS(段寄存器),CONTEXT_DEBUG_REGISTERS(调试寄存器),CONTEXT_EXTENDED_REGISTERS(扩展寄存器),CONTEXT_FLOATING_POINT(浮点寄存器).
我们可以通过,GetThreadContext函数:
BOOL GetThreadContext(
HANDLE hThread,
PCONTEXT pContext
);
调用这个函数前需要先调用SuspendThread!否则,系统可能正好获得调度此线程,这样一来,线程的上下文与所获取的信息就不一致了。
CONTEXT结构的ContextFlags成员与任何cpu寄存器都不对应。这个成员的作用是告诉GetThreadContext函数,应该获取哪些寄存器。所以,初始化CONTEXT结构时,要先初始化ContextFlags成员。
如果想改变那个线程的上下文,可以调用SetThreadContext函数。使用规则和GetThreadContext类似。
线程优先级
每个线程都被赋予0(最低)~31(最高)的优先级。当系统确定给哪个线程分配CPU时,它会首先查看优先级为31的线程,并以循环的方式进行调度。如果有优先级为31的线程可供调度,那么系统就会将CPU分配给该线程。
只要有优先级为31的线程可供调度,系统就不会为0~30的线程分配CPU。这种情况称为饥饿。
较高优先级的线程总是会抢占较低优先级的线程,无论较低优先级的线程是否正在执行。例如,有一个优先级为5的线程正在运行,而系统确定有较高优先级的线程已准备好可以运行,它会立即暂停较低优先级的线程(即使其时间片还没有用完),并将CPU分配给较高优先级的线程,该线程获得一个完整的时间片。
系统启动时,会创建一个名为页面清零线程的特殊线程。这个线程优先级定为0,这是整个系统中唯一一个优先级为0的线程。页面清零线程负责在没有其他进程需要执行时,将系统内存中的所有闲置页面清零。
基于Windows的优先级抽象层
Windows进程支持6个优先级类:idle,below normal,normal,above normal,high和real-time。normal是最常用的,为99%的应用程序所使用。
real-time:立即响应事件,会抢占操作系统组件的cpu时间,使用时要极为小心。
high:立即执行,任务管理器运行在这一级,所以用户可以通过它结束失控的进程。
above normal:此进程中的线程运行在normal和high优先级类之间。
normal:此进程无需特殊的调度。
below normal:此进程中的线程运行在normal和idle优先级类之间。
idle:此进程中的线程在系统空闲时运行。屏幕保护程序、后台实用程序和统计数据收集软件通常使用该进程。
Windows支持7个相对线程优先级:idle,lowest,below normal,normal,above normal,highest和time-critical。这些优先级是相对于进程优先级的。大多数线程使用normal线程优先级。
读者可能注意到,我们并没有提及0~31优先级。应用程序的开发人员无需处理优先级,而是由系统将进程的优先级类和线程的相对优先级映射到一个优先级值。详情对应表可以上网进行查询。
总之,real-time优先级类的线程,其优先级值不能低于16。同理,非real-time优先级线程的优先级值不能高于15。
优先级编程
调用CreateProcess函数时,给fdwCreate参数传入需要的优先级标识符,比如:normal对应NORMAL_PRIORITY_CLASS。
还可以调用BOOL SetPriorityClass(
HANDLE hProcess,
DWORD fdwPriority);
修改句柄所标识的进程或线程的优先级。
修改线程优先级的函数是SetThreadPriority函数。
*注意,CreateThread函数创建的总是优先级为normal的新线程。要改变线程优先级,需要在创建的时候让线程挂起,然后我们调用SetThreadPriority修改其优先级,接着调用ResumeThread,线程变为可调度的。我们不知道什么时候线程可以获得cpu时间,但是调度程序会考虑这个线程的优先级。最后,关闭新线程的句柄,这样一来,一旦线程终止,系统就可以销毁内核对象。*
DWORD dwThreadID;
HANDLE hThread = CreateThread(NULL,0,ThreadFunc,NULL,CREATE_SUSPENDED,&dwThreadID);
SetThreadPriority(hThread,THREAD_PRIORITY_IDLE);
ResumeThread(hThread);
CloseHandle(hThread);
动态调整线程优先级 ##
系统会自动提升优先级值在1~15的线程。当动态提升后,每一个时间片执行结束,优先级都会递减1,同时,线程的当前优先级不会低于线程的基本优先级。
注:当某个线程处于饥饿状态时,系统探查到有已经处在饥饿状态3到4秒时,会动态提升其优先级,并允许其运行两个时间片。当两个时间片结束后,该线程恢复到基本优先级。
io请求优先级
调用SetThreadPriority并传入THREAD_MODE_BACKGROUND_BEGIN来告诉Windows,线程应该发送低优先级的I/O请求。注意,这也将降低线程的CPU调度优先级。我们还可以传入THREAD_MODE_BACKGROUND_END,让线程进行normal优先级I/O的请求(以及normalCPU调度优先级)。系统不允许线程改变另一个线程的I/O优先级,所以调用SetThreadPriority时是传入的GetCurrentTHREAD返回的句柄。
关联性
默认情况下,系统使用软关联,即其他因素都一样,系统将使线程在上一次运行的处理器上运行。让线程始终在同一个处理上运行有助于重用人在处理器高速缓存中的数据。
我们控制CPU让哪些CPU运行特定的线程。这称为硬关联。
如果我们想要某些线程只在某个CPU上运行,则可以调用
SetProcessAffinityMask(
HANDLE hProcess
DWORD_PTR dwProcessAffinityMask
)
第一个参数hProcess代表要设置的进程。第二个参数dwProcessAffinityMask是一个位掩码,代表线程可以在哪些CPU上运行。例如,传入0x00000005意味着可以在CPU0和CPU2上运行,但是不能在CPU1和CPU3~CPU31上运行。子进程将继承进程相关性。P193
可以使用SetThreadAffinityMask设置某一个线程在哪一组cpu上
[cpp] view plain copy
01.// 返回值是线程之前的掩码;
02.DWORD_PTR SetThreadAffinityMask(
03. HANDLE hThread, // 线程句柄;
04. DWORD_PTR dwThreadAffinityMask // 关联性掩码;
05. );
还可以使用
DWORD SetThreadIdealProcessor(
HANDLE hThread,
DWORD dwIdealProcessor
);
这个函数。其中:hThread表示要设置CPU的线程。但是和之前的函数不同的是,dwIdealProcessor不是位掩码,踏实一个0-31/63之间的整数,表示线程希望设置的CPU。可以传入MAXIMUM_PROCESSORS值,表示没有理想CPU。函数返回之前的理想CPU。如果没有理想CPU则返回MAXIMUM_PROCESSORS。
所谓的理想CPU是指我们希望这个线程运行在哪个CPU上,但是系统也允许将它移到另一个空闲的CPU。
另外任务管理器也可以设置CPU关联性。
最后
以上就是强健小兔子为你收集整理的线程调度、优先级和关联性(7)的全部内容,希望文章能够帮你解决线程调度、优先级和关联性(7)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复