代码改变世界

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

2018-01-31 20:49  szn好色仙人  阅读(600)  评论(0编辑  收藏  举报
//1.
每隔一段时间, Windows 会查看所有当前可调度的线程内核对象。在这些对象中,只有一些被认为是可调度的
Windows 在这些可调度的线程内核对象中选择一个,并将上次保存在线程上下文的值载入CPU寄存器。这一操作被称为上下文切换。 Windows 会记录每个线程运行的次数

//2.
(A):SuspendThread ResumeThread 可以用来挂起和恢复线程,注意:线程可以被挂起多次,要使其恢复则需调用对应次数的 ResumeThread
(B):上述两个函数都返回线程的前一个挂起计数,一个线程最多挂起 MAXIMUM_SUSPEND_COUNT(127)次
(C):就内核模式下执行情况而言, SuspendThread 是异步的,但在线程恢复前,他是无法在用户模式下执行的
(D):实际开发中,应用程序调用 SuspendThread 必须十分小心,当挂起了一个线程,而此线程比如正在分配堆的内存,此时线程将锁定堆,当其他线程想访问堆的时候,他们的操作将被阻塞,直到第一个线程恢复

//3.
适用于大部分情况的 暂停一个进程所有线程的函数
#include <TlHelp32.h>
void SuspendProcessOwn(const unsigned int nProcessIdC, const bool bSuspendC)
{
	HANDLE hFind = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, nProcessIdC);

	if (INVALID_HANDLE_VALUE != hFind)
	{
		THREADENTRY32 th = {sizeof THREADENTRY32};
		BOOL nRe = Thread32First(hFind, &th);
		while(nRe)
		{
			if (th.th32OwnerProcessID == nProcessIdC)
			{
				HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, th.th32ThreadID);
				if (hThread)
				{
					if (bSuspendC)
					{
						SuspendThread(hThread);
					}
					else
					{
						ResumeThread(hThread);
					}

					CloseHandle(hThread);
				}
			}

			nRe = Thread32Next(hFind, &th);
		}

		CloseHandle(hFind);
	}
}
//上述函数是有缺陷的,当被暂停的进程在被调用此函数的同时刚好在起线程,就会导致一些问题,当然,概率很低

//4.
Sleep:
(A):调用此函数,将使得线程自愿放弃属于他的时间片的剩余部分
(B):系统设置线程不可调度的时间只是近似于所设定的毫秒,比如 Sleep 100ms,但是有可能会长达数秒或数分钟,因为 Windows 不是实时操作系统
(C):可以传递 INFINITE, 表示无限 Sleep
(D):可以传入0,告诉系统,主调线程放弃了时间片的剩余部分,它强制系统调度其他线程。但是系统仍可能会重新调用此线程(如果没有相同优先级或更高优先级的可调度线程存在时就会发生这种情况)

//5.
SwitchToThread
(A):如果存在另一个可调度线程,那么系统会让此线程运行
(B):如果在调用此函数时没有其他线程可以运行,则返回 FALSE, 否则返回一个非0值
(C):调用此函数与调用 Sleep(0); 类似,然而 SwitchToThread 允许低优先级线程执行,然而 Sleep(0)会立刻重新调用主调线程,即使仍有低优先级线程处于饥饿状态

//6.
GetThreadTimes 可以得到线程创建时间、退出时间、内核时间、用户时间
GetProcessTimes 得到的是所有线程的统计总量,包括已退出的线程

QueryPerformanceCounter
QueryPerformanceFrequency 以上两个函数可以用于高精度下的情况

//7.
CONTEXT 结构是特定于CPU的

//8.
(A):每个线程都被赋予0-31的优先级数。当较高优先级线程占用了CPU时间,致使较低优先级的线程无法运行时,这种情况称为饥饿
(B):较高优先级的线程总是会抢占较低优先级的线程,无论较低优先级的线程是否正在执行

//9.
Windows 支持六个优先级类:(用于进程,可以在调用 CreateProcess 时指定优先级)
Idle(IDLE_PRIORITY_CLASS):此进程中的线程在系统空闲时运行,如屏幕保护程序等
below normal(BELOW_NORMAL_PRIORITY_CLASS):在 normal 和 Idle 之间
normal(NORMAL_PRIORITY_CLASS):大部分应用的优先级,无需特殊调度
above normal(ABOVE_NORMAL_PRIORITY_CLASS):normal 和 high 之间
high(HIGH_PRIORITY_CLASS):此进程中的线程必须立即响应事件,执行实施任务。任务管理器运行在这一级。只有在绝对必要的时候才使用此优先级
real-time(REALTIME_PRIORITY_CLASS):此进程中的线程必须立即响应事件,执行实时任务,此进程中的线程还会抢占操作系统的组件CPU时间。使用该优先级务必小心,应尽可能避免使用

SetPriorityClass GetPriorityClass 可以设置、获取进程优先级
可以直接在任务管理器中为某个进程设置优先级

//10.
Windows 支持七个相对线程优先级:
time-critical(THREAD_PRIORITY_TIME_CRITICAL):对于 real-time 优先级类,线程运行在31上,所有其他优先级运行在15
highest(THREAD_PRIORITY_HIGHEST):高于 normal 2个级别
above normal(THREAD_PRIORITY_ABOVE_NORMAL):高于 normal 一个级别
normal(THREAD_PRIORITY_NORMAL):normal 级别
below normal(THREAD_PRIORITY_BELOW_NORMAL):低于 normal 一个级别
lowest(THREAD_PRIORITY_LOWEST):低于 normal 两个级别
idle(THREAD_PRIORITY_IDLE):对于 real-time 优先级类,线程运行在16,所有其他优先级运行在1

SetThreadPriority GetThreadPriority 可以设置、获取相对线程优先级

//11.
一般而言,有较高优先级的线程大多数时候应该是不可调度的。当这种线程要执行任务时,能很快获得CPU时间。这时线程应该尽可能少的执行CPU指令,并重新睡眠
优先级低的线程可以保持为可调度状态,执行大量CPU指令以完成任务。
如此,操作系统就能够很好的响应用户

//12.
当一个低优先级线程处于CPU饥饿状态3到4秒时,系统会自动将饥饿线程的优先级提升为15,并允许其运行2个时间片,之后再恢复其优先级
系统只会提升优先级值在0-15的线程(自动的)。这个范围被称为动态优先级范围(高于15就是实时范围了)
可以禁止系统动态提升线程优先级,系列函数如下: SetProcessPriorityBoost SetThreadPriorityBoost GetProcessPriorityBoost GetThreadPriorityBoost

//13.
线程可以在进行I/O请求时设置优先级。调用 SetThreadPriority 并传入 THREAD_MODE_BACKGROUND_BEGIN 来告诉系统,线程应该发送低优先级I/O请求,注意:这也将降低线程的CPU调度优先级
我们可以调用 SetThreadPriority 并传入 THREAD_MODE_BACKGROUND_END ,让线程进行 normal 优先级I/O请求(以及 normal CPU调度优先级)
系统不允许线程改变另一个线程的I/O优先级

SetPriorityClass PROCESS_MODE_BACKGROUND_BEGIN PROCESS_MODE_BACKGROUND_END, 可以设置进程中的所有线程的I/O优先级
系统不允许进程改变另一个进程的线程的I/O优先级

normal 优先级线程还可以执行对某个文件执行后台优先级I/O, 利用 SetFileInformationByHandle 设置的优先级会覆盖进程或者线程的优先级

//14.
我们可以设置进程和线程的关联性,来控制CPU运行特定的线程。这称为硬关联
一般线程会继续使用上次用的CPU。这称为软关联

GetSystemInfo 可以查询包括CPU数目在内的系统信息

SetProcessAffinityMask GetProcessAffinityMask SetThreadAffinityMask GetThreadGroupAffinity
以上函数第二个参数为掩码,比如0x05代表能在CPU0和CPU2上运行
进程可运行的CPU集合必须是系统CPU集合的真子集,线程可运行的CPU集合必须是进程CPU集合的真子集

给线程设置理想CPU,使用 SetThreadIdealProcessor

使用任务管理可以给进程设置CPU硬关联