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

来源:互联网 发布:网络论坛 编辑:程序博客网 时间:2024/05/01 12:36

1:基础

1.1:每个线程都有个上下文,存储了线程寄存器状态,windows每隔20ms左右查看所有的线程内核对象,找到其中一个可调度线程,将它的上下文载入CPU寄存器,这个过程被称为context switch,Windows会记录每个线程运行的次数

Windows是抢占式多线程操作系统,所以不可能精准的控制线程的执行时刻

2:线程的挂起和恢复

2.1:调用CreateProcess()或者CreateThread()创建了一个线程内核对象时,其挂起计数为1,这样做是因为线程还未被完全创建,不希望操作系统此时调用它,创建完成后,系统查看CreateProcess()或者CreateThread()是否传入了CREAT_SUSPENDED参数,如果有,则保持当前挂起计数,如果没有则将挂起计数-1,当挂起计数为0时,线程可以被调度

DWORD ResumeThread(HANDLE hd);将线程内核对象挂起计数-1,并返回前一个挂起计数

DWORD SuspendThread(HANDLE hd);将线程内核对象挂起计数+1,并返回前一个挂起计数

2.2:一个线程最多被挂起MAXIMUM_SUSPEND_COUNT,也就是127次

2.3:SuspendThread()对于内核模式而言是异步的,小心使用这个函数,比如如果被挂起的函数停留在malloc中时,其他线程也在分配内存时,也将被阻塞

3:挂起一个进程中所有的线程

3.1:调试中有相关的特性,但Windows没有提供这样的函数,因为存在竞态条件问题:在线程被挂起时,可能会创建新的进程,系统必须向办法挂起新创建的线程

3.2:一个简易版本的挂起进程所有线程函数:

#include "Tlhelp32.h"#pragma comment(lib,"Kernel32.lib")void SuspendProcess(DWORD dwProcessID,bool bSuspend){HANDLE hdSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,dwProcessID);if(hdSnapshot==INVALID_HANDLE_VALUE)return;THREADENTRY32 te={sizeof(te)};bool bOK=Thread32First(hdSnapshot,&te);while(bOK){if(te.th32OwnerProcessID!=dwProcessID){HANDLE hdThread=OpenThread(THREAD_SUSPEND_RESUME,FALSE,te.th32ThreadID);if(hdThread){if(bSuspend)SuspendThread(hdThread);elseResumeThread(hdThread);CloseHandle(hdThread);}}bOK=Thread32Next(hdSnapshot,&te);}CloseHandle(hdSnapshot);}

这个版本有几个问题:

1:在挂起进程所有线程的时候,如果创建了新线程,则不会被挂起,恢复的时候其挂起计数会被错误的-1

2:挂起的时候,可能一个线程正在被销毁,恢复线程的时候会出问题

这2个问题在调用此函数的时候应该考虑到,并在绝对安全的时候调用它

4:Sleep(DWORD dwTime)

Sleep函数会让线程近似挂起dwTime毫秒,如果给dwTime传入0,表明线程放弃剩余时间片,操作系统会重新调度一个线程但有可能重新调度的这个线程就是刚刚Sleep(0)的这个线程

5:SwitchToThread()

此函数会让操作系统去寻找一个饥饿线程,如果找到则调度即使这个饥饿线程优先级比当前线程低,如果没有找到则返回

与Sleep的区别就在于,如果找到一个线程,但优先级比调用线程低,Sleep()会执行调度线程,SwitchToThread()会执行优先级低的线程

这个函数应用场所是:当一个优先级低的线程占用了资源,优先级高的资源可以调用此函数放弃剩余时间片,让操作系统调度低优先级线程,从而给予低优先级线程释放资源的机会

6:获得一个算法执行时间

6.1:使用ULONGLONG ullStartTime=GetTickCount64();//int版本是GetTickCount();

 这个函数包含了线程切换的时间,所以精度相对比较低

6.2:使用GetThreadTimes()

class GetAlgorithmTimes{public:GetAlgorithmTimes(){ZeroMemory(this,sizeof(this));}voidStart(){GetThreadTimes(GetCurrentThread(),&m_ftCreateTime,&m_ftExitTime,&m_ftKernalTimeStart,&m_ftUserTimeStart);}voidEnd(){FILETIMEftKernalTimeEnd;FILETIMEftUserTimeEnd;GetThreadTimes(GetCurrentThread(),&m_ftCreateTime,&m_ftExitTime,&ftKernalTimeEnd,&ftUserTimeEnd);m_KernalTime=TransFileTime(&ftKernalTimeEnd)-TransFileTime(&m_ftKernalTimeStart);m_UserTime=TransFileTime(&ftUserTimeEnd)-TransFileTime(&m_ftUserTimeStart);}private:__int64TransFileTime(PFILETIME pft){return (Int64ShllMod32(pft->dwHighDateTime,32)|pft->dwLowDateTime);}public:FILETIMEm_ftCreateTime;FILETIMEm_ftExitTime;FILETIMEm_ftKernalTimeStart;FILETIMEm_ftUserTimeStart;__int64m_KernalTime;__int64m_UserTime;};

与之相对应的还有GetProcessTimes()函数

6.3:使用更高精度函数

LONGLONG   t1,t2;  LONGLONG   persecond;  QueryPerformanceFrequency((LARGE_INTEGER*)&persecond);  QueryPerformanceCounter((LARGE_INTEGER*)&t1);  //代码   QueryPerformanceCounter((LARGE_INTEGER*)&t2);  double time=(t2-t1)/persecond; 

7:CONTEXT结构

可以调用GetThreadContext(HANDLE hd,PCONTEXT ct)和SetThreadContext(HANDLE hd,PCONTEXT ct)查询和控制上下文,在调用之前应该调用SuspendThread()挂起线程,防止线程上下文状态发生变化,一个线程实际有两个上下文:用户模式上下文,内核模式上下文,这两个函数控制的是用户模式上下文,虽然SuspendThread()在内核中是异步的,但调用后,线程不会再执行用户模式代码,所以这是安全的

给这两个函数必须传递一个CONTEXT结构参数,并设置其ContextFlags标志,用以告诉操作系统查询和修改那一类的寄存器

可以修改另外一个线程CONTEXT结构,乱改会使其崩溃

8:线程优先级
8.1:CPU给每个线程执行时间大约20ms,线程优先级从0(最低)~31(最高),如果系统中有高优先级线程,就绝对不会去调用低优先级线程,这些低优先级线程会处于饥饿状态(备注:这种情况下会发生动态提升优先级情况,参见本章11)

8.2:优先级高的线程会抢占优先级低的线程,即使优先级低的线程时间片还未用完,这也是Windows被称为抢占式多线程操作系统的原因

8.3:系统启动时,会创建一个页面清0线程,优先级0,职责是在没有其他线程运行时,将所有闲置页面清零

9:从抽象角度看优先级

9.1:可以给进程定义优先级,"给进程定义优先级"是一个Windows抽象概念,只有线程才有真正的优先级概念,因为只有线程才能被调度,Windwos支持6个优先级类

9.2:可以给线程定义相对进程优先级,一个线程的优先级取决于进程优先级和线程优先级的叠加,这个叠加就是真正的优先级(0~31之间)

9.3:用户不能定义0优先级线程,有些优先级给驱动开发使用,同样不能定义

10:优先级编程

10.1:进程优先级从高到低如下

REALTIME_PRIORITY_CLASSHIGH_PRIORITY_CLASSABOVE_NORMAL_PRIORITY_CLASSNORMAL_PRIORITY_CLASSBELOW_NORMAL_PRIORITY_CLASSIDLE_PRIORITY_CLASS

BOOL SetPriorityClass(HANLDE hd,DWORD dwPriority)

DWORD GetPriorityClass(HANDLE hd)

CreateProcess()可以设置进程优先级

10.2:线程相对进程优先级从高到低如下

THREAD_PRIORITY_TIME_CRITICALTHREAD_PRIORITY_HIGHESTTHREAD_PRIORITY_ABOVE_NORMALTHREAD_PRIORITY_NORMALTHREAD_PRIORITY_BELOW_NORMALTHREAD_PRIORITY_LOWESTTHREAD_PRIORITY_IDLE

BOOL SetThreadPriority(HANDLE hd,DWORD dwPriority)

int GetThreadPriorit(HANDLE hd)

CreateThread()没有设置线程相对进程优先级的参数,这是微软脑残了

如果需要创建一个XX优先级的线程,应该再创建函数中设置CREATE_SUSPENDED参数,然后设置优先级,在ResumeThread();

10.3:Windows只提供了查询和设置线程相对进程优先级函数,而不提供查询和设置线程优先级函数,注意二者的区别

11:动态提升线程优先级

11.1:对于优先级为1~15的线程,Windows会动态提升线程优先级以保证线程的高效运行,Windows的这种微调只会提升而不会降低线程基本优先级(也就是说会对提升的部分进行降低)

11.2:禁止Windows提升进程所有线程优先级函数和禁止提升线程优先级函数如下:

BOOL SetProcessPriorityBoost(HANDLE hProcess,BOOL  bDisablePriorityBoost); BOOL GetProcessPriorityBoost(HANDLE hProcess,PBOOL pDisablePriorityBoost);BOOL SetThreadPriorityBoost(HANDLE hThread,BOOL  bDisablePriorityBoost);BOOL GetThreadPriorityBoost(HANDLE hThread,PBOOL pDisablePriorityBoost);

11.3:如果一个高优先级线程一直处于可调度状态,一个低优先级线程就没有机会被调度,系统检测到这种情况,当低优先级线程饥饿3~4s时,系统会提升饥饿线程优先级到当前可调度优先级,并让其运行2个时间片,时间片结束后,系统恢复其优先级为原来的优先级

11.4:如果一个进程创建了一个窗口,这个进程就称为前台进程,当其进程优先级是normal时,系统会为前台进程中的线程微调其调度算法,使其线程运行时分配更多的时间片

12:优先级逆转

12.1: 一个低优先级线程执行了大量低速操作,并且这些操作必须进行线程同步(如IO操作),从而使高优先级线程等待这些操作完成,这就是优先级逆转

12.2:Vista中可以设置IO优先级

13:线程关联性

13.1:获得CPU数量

GetSystemInfo();

13.2:设置和查询进程关联性

BOOL SetProcessAffinityMask(HANDLE hd,DWORD_PTR dwProcessAffinityMask);//设置进程关联性

dwProcessAffinityMask比如传入0x00000003表示进程可以在CPU0和CPU1中运行

BOOL GetProcessAffinityMask(HANDLE hd,DWORD_PTR pdwProcessMask,DWORD_PTR pdwSystemMask);//获得进程关联性

pdwSystemMask是系统可用CPU集合,pdwProcessMask是pdwSystemMask的一个真子集

13.3:设置和查询线程关联性

线程关联性必须是进程关联性的一个真子集

DWORD_PTR SetThreadAffinityMask(HANDLE hd,DWORD_PTR dwThreadMask);

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