windows 线程调度

来源:互联网 发布:网络电视用什么播放器 编辑:程序博客网 时间:2024/05/03 18:44

windows 是一种抢占式式的多线程操作系统(preemptive multithreaded operating system) 。所以windwos 会成千上完个线程中进行快速的切换,这样它们就看起来想是同时运行了。而为了完成这个我们的就需要降到上下文切换(context switch)。

每一个线程都有一个内核对象。内核对象中有一个数据结构就在CONTEXT(WinNT.h中定义了)。当操作系统准备切换到下一个线程的时候就会将cpu 寄存器中的信息存储到CONTEXT机构中去。当下一次重新调用该线程的时候,系统会重新把CONTEXT中的信息放回cpu的寄存器上。这样就回到了之前的状态。

上下文的切换时间可以使用GetSystemAdjustment函数的第二个参数获得

我们来看看如何获得上下文切的相关信息。
首先windows visual studio 为我们提供了一个叫做spy++的工具
这里写图片描述

然后我们点击工具栏的监视->进程
这里写图片描述

随便点开一个进程,然后随便右键一个线程 选择属性
这里写图片描述

上下文开关
实际就上下文切换的次数。如果想通过编程的方式查找的话,请自行查看Performance Counters

后面会慢慢讲解这写数据

线程状态

对操作系统来说线程只会有两种状态。一种是可运行状态,操作系统只在这种类型的线程中进行上下文切换。第二种状态是挂起状态,这种状态的线程会被打入冷宫,系统不会运行该线程。

而等待就是通过线程的挂起来实现的。要等待的线程会先被挂起,知道某一个特点的时间触发系统才会把该线程编程可运行状态。

线程的挂起和回复

挂起和回复线程我们可以通过SuspendThreadResumeThrad来进行

WINBASEAPIDWORDWINAPISuspendThread(    _In_ HANDLE hThread    );WINBASEAPIDWORDWINAPIResumeThread(    _In_ HANDLE hThread    );

需要注意的时线程的挂起标志是一个数字表示线程被挂起了多少次,所以如果我们手动控制线程的挂起的话,必须要注意SuspendThread和ResumeThread的成对性

对于进程来说时没有挂起和回复的概念的。windows 在发行版中是没有提供想要的API的,如果我们通过Tool help 或者其他函数来枚举进程的中的线程然后一一挂起的话,是不一定完美工作的。因为在枚举的期间可能会创建或者销毁新的线程。但是在debug版本是存在相应的函数。就是WaitForDebugEvent返回调试事件时,会让挂起线程中的所有进程知道调用ContinueDebugEvent

线程的睡眠

同样有一种通过挂起而实现的功能就是睡眠。

WINBASEAPIVOIDWINAPISleep(    _In_ DWORD dwMilliseconds    );

传递一个毫秒数哦!windows 只确表在顶用指定事件之后线程会编程可运行状态,但是之后什么时候会被调用就不保证了。

传入INFINITE,永远不会调用这个线程,这一般不会使用,预期这样不让让线程自然退出

传入0,然此线程变成可运行状态,然后调用别的线程去,又可能重新调用自己。

线程的切换

WINBASEAPIBOOLWINAPISwitchToThread(    VOID    );

系统会产看是否有急需cpu时间的“饥饿”线程。如果有函数立即返回。然后调用该“饥饿”线程。“饥饿线程”运行完毕之后系统的调度执行回复正常。

和Sleep(0)不同之处是。Sleep(0)的线程调度是正常的,也就是说“饥饿”低优先权线程可能不会的到调度

获得线程执行的时间

(1)
先介绍一个给函数GetTickCount64函数。干函数返回档期那毫秒时间。

    ULONGLONG starttime = GetTickCount64();    //do someting;    ULONGLONG endtime = GetTickCount64();    ULONGLONG usedtime = endtime - starttime;

但是如果cpu在此期间进行了上下文切换就会很不准确。不光如此。

The resolution of the GetTickCount64 function is limited to the resolution of the system timer, which is typically in the range of 10 milliseconds to 16 milliseconds. The resolution of the GetTickCount64 function is not affected by adjustments made by the GetSystemTimeAdjustment function.

GetTickCount64中”Tick”单词说明该函数增长的单位是受限于系统钟频率,一般在10到16毫秒。而且它不受GetSystemTimeAdjustment 调整的影响。

(2)
因为上面的缺点我们就要说到另一个函数了GetThreadTimes

WINBASEAPIBOOLWINAPIGetThreadTimes(    _In_ HANDLE hThread,    _Out_ LPFILETIME lpCreationTime,    _Out_ LPFILETIME lpExitTime,    _Out_ LPFILETIME lpKernelTime,    _Out_ LPFILETIME lpUserTime    );

lpCreationTime
因为是设备时间所以是10E-4毫秒(100纳秒),下面相同

线程创建时间
lpExitTime
线程退出时间
lpKernelTime
线程核心模式下使用的时间
lpUserTime
线程用户模式下使用的时间

有了这个函数我们就可以不用担心上下文切换而造成误差了

(3)
在windows vista和之后的版本,开始使用64位时间戳计时器(Time stamp counter,TSC)代替间隔时钟计时器。而TSC是一个计算器启动以来的时钟周期。我们可以使用WinNT.h中的ReadTimeStampCounter宏来查看这个值。或者c运行库的_rdtsc函数。

为了高精度的性能分析windows提供了performance counter。这个依赖与TSC支持。
QueryPerformanceFrequency来查高精度计时器频率(秒位单位),返回0表示没有硬件(TSC)的支持
QueryPerformanceCounter :当前高精度计器当时计数

CONTEXT机构的控制

我们可以通过GetThreadContext和SetThreadContext来取得和设置Context。但是必须把传入一个初始化了CONTEXT.ContextFlags的CONTEXT的结构好告诉系统是取得还是修改那些cpu寄存器

线程优先级

一个线程的优先级是和所属进程优先级和自身优先级相关的。

进程优先级由低到高

idle
below normal
normal
above normal
high
real-time

为CreateProcess的fdwCreate参数传递*_PRIORITY_CLASS

子进程会继承父进程的优先级(所以通过windows 资源管理器启动的程序都是normal,命令行启动的同样也是)
但是我们可以使用可以使用SetPriorityClass和GetPriorityClass函数更改和进程的优先级

线程优先级由低到高
idle
lowest
below normal
normal
above normal
highest
time-critical

因为创建线程的函数不能设置优先级,所以线程启动的默认优先级是normal。但是我们可以调用SetThreadPriority函数来设置,参数为THREAD_PRIORITY_*。淡然还有GetThreadPriority

我们还可以通过SetPriorityBoost来禁止线程优先级的自动提升

原创粉丝点击