Windows 线程(十一) 线程优先级
线程优先级
在单核的条件下, 我们分析一下线程的优先级
线程优先级的级别
- Windows的每一个
可调度的线程都有一个优先级(0--31) - 当系统准备去执行一条线程时, 会首先看优先级为
31的线程 - 系统以
轮询的方式来进行调度 - 只要有优先级为
31的线程, 操作系统就会去调用 - 如果存在优先级为
31的线程, 操作系统将永远不会调用30以下的线程
线程的可调度状态
通过以上描述, 我们发现貌似优先级较低的线程都会永远得不到执行机会
其实不是, 系统中的大部分线程都是属于不可调度状态的
不可调度状态的线程, 比如说
休眠后的线程- 它们会被从调度区进入不可调度区
- 可以通过Sleep函数让线程进入
休眠状态 调用了GetMessage后, 线程也会休眠, 从而导致线程不可调度
- 注意, 被
挂起的线程不属于不可调度状态 - 因为它们会被从CPU的调度池中剥离出去
- 属于
不可调度状态的前提, 是线程在CPU调度范围内
- 注意, 被
// CPU瞬间100%的代码
#include <Windows.h>
#include <cstdio>
int main()
{
// 设置进程在1号CPU中执行 也就是限制单核
SetProcessAffinityMask(GetCurrentProcess(), 0x1);
while(true)
{
// 一个空的循环
// 放开printf CPU反而不会100%
// printf("1");
}
}以上代码的原理就是, 空while会导致线程一直处于可调度状态
而调用了API后的线程会进行等待, 从而进入不可调度状态
线程优先级注意事项
需要注意的是, 优先级较高的线程总是会抢占优先级较低的线程的时间片
- 无论优先级较低的线程是否在执行属于自己的时间片
- 优先级较高的线程在准备好可以运行的时候
- 会直接打断优先级较低的线程的执行顺序
- 将CPU时间片分配给优先级较高的线程
- 这个行为是
CPU允许的
我们不可以自己设置线程优先级为0
- Windows中有一个优先级为
0的线程 - 它在系统启动后自动启动
- 它是
页面清零线程(Zero Page Thread) 它主要负责在
系统空闲时清理闲置的内存, 对闲置内存清零- 系统空闲是指
所有线程都处于不可调度状态 - 这是很有可能发生的事情
- 系统空闲是指
进程优先级
我们都知道, 进程只是提供了代码和数据, 具体的执行还是靠线程
但是为什么会有进程优先级这种东西呢?
因为Windows需要不听的进行进程切换, 或者选择某一进程进行运行
所以Windows对进程提供了优先级的设置
进程优先级分为如下级别
- real-time(实时)
- high(立即/高)
- above normal(较高/高于正常)
- normal(正常)
- below normal(较低/低于正常)
- idle(低)
不应该有任何的进程运行在实时优先级
- 实时优先级可能会影响到操作系统的任务
- 可能导致磁盘, 网络通信, 键盘, 鼠标等使用
进程优先级和线程优先级的关系
进程的优先级将会影响线程中的优先级
下面一个表格列举它们之间的关系
<!--
| 线程优先级 | real-time(实时) | high(立即/高) | above normal(较高/高于正常) | normal(正常) | below normal(较低/低于正常) | idle(低) |
|---|---|---|---|---|---|---|
| time-critical | 31 | 15 | 15 | 15 | 15 | 15 |
| highest | 26 | 15 | 12 | 10 | 8 | 6 |
| above normal | 25 | 14 | 11 | 9 | 7 | 5 |
| normal | 24 | 13 | 10 | 8 | 6 | 4 |
| below normal | 23 | 12 | 9 | 7 | 5 | 3 |
| lowest | 22 | 11 | 8 | 6 | 4 | 2 |
| idle | 16 | 1 | 1 | 1 | 1 | 1 |
-->
| 进程优先级 | ||||||
|---|---|---|---|---|---|---|
| 线程优先级 | real-time(实时) | high(立即/高) | above normal(较高/高于正常) | normal(正常) | below normal(较低/低于正常) | idle(低) |
| time-critical(实时) | 31 | 15 | 15 | 15 | 15 | 15 |
| highest(高) | 26 | 15 | 12 | 10 | 8 | 6 |
| above normal(较高/高于正常) | 25 | 14 | 11 | 9 | 7 | 5 |
| normal(正常) | 24 | 13 | 10 | 8 | 6 | 4 |
| below normal(较低/低于正常) | 23 | 12 | 9 | 7 | 5 | 3 |
| lowest(低) | 22 | 11 | 8 | 6 | 4 | 2 |
| idle(空闲) | 16 | 1 | 1 | 1 | 1 | 1 |
该表格表示的是进程优先级下的线程各个优先级对应的级别
可以看到, 当进程优先级为实时时, 最低等级的线程优先级都是16
比其他进程优先级下的最高线程优先级都高
所以,不要出现运行于实时优先级下的进程
- 0优先级为系统保留的
页面清零线程 列表中未出现的
17--21, 27--30保留给系统内核使用- 他们将用于进行IO等其他系统操作
- 默认启动的进程都是
normal优先级 Windows资源管理器进程优先级为
high- 是为了实时关闭进程
优先级相关API
CreateProcess可以设置进程优先级
- 它其实是通过SetPriorityClass来改变自己的优先级
- GetPriorityClass可以获取当前进程优先级
- SetThreadPriority可以改变线程优先级
- GetThreadPriority可以获取线程优先级
特别值得注意的是, CreateThread无法设置线程优先级, 线程默认是以normal优先级运行的
操作系统的动态提升优先级
因为线程是抢占式执行的
线程的优先级越高, 就能越多的抢占CPU时间片
这样就造成了一些低优先级的进程饥饿的情况
还有, 就是在系统进行IO操作的时候, 可能会CPU空闲
针对这种情况, 操作系统会动态提升一些线程的优先级
- 系统默认将线程的优先级
提升两级 - 在操作完成一个时间片后
递减1 - 直到恢复原来的优先级
- 当提升一次无法达到预期效果时, 系统会继续提升优先级
- 动态调整的线程要求优先级在
1--15之间 - 每次调整的最大值不会超过
15
// 系统提升线程优先级的例子
#include <Windows.h>
#include <process.h>
#include <cstdio>
ULONG64 g_nNum1 = 0, g_nNum2 = 0, g_nNum3 = 0;
UINT WINAPI ThreadFun1(LPVOID lParam)
{
while (TRUE)
{
++g_nNum1;
}
return 0;
}
UINT WINAPI ThreadFun2(LPVOID lParam)
{
while (TRUE)
{
++g_nNum2;
}
return 0;
}
UINT WINAPI ThreadFun3(LPVOID lParam)
{
while (TRUE)
{
++g_nNum3;
}
return 0;
}
INT main()
{
HANDLE hThreads[3] = { INVALID_HANDLE_VALUE };
hThreads[0] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun1, (LPVOID)1, CREATE_SUSPENDED, NULL));
SetThreadPriority(hThreads[0], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[0]);
hThreads[1] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun2, (LPVOID)2, CREATE_SUSPENDED, NULL));
SetThreadPriority(hThreads[1], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[1]);
hThreads[2] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun3, (LPVOID)3, CREATE_SUSPENDED, NULL));
SetThreadPriority(hThreads[2], THREAD_PRIORITY_IDLE);
ResumeThread(hThreads[2]);
INT nExecTimes = 0;
while (nExecTimes++ < 10)
{
WaitForMultipleObjects(3, hThreads, TRUE, 1000);
// 此例子就不在做线程同步了
printf("g_Num1:[%lld]\ng_Num2:[%lld]\ng_Num3:[%lld]\n---------\n", g_nNum1, g_nNum2, g_nNum3);
}
for (INT i = 0; i < 3; ++i)
{
CloseHandle(hThreads[i]);
}
return 0;
}
当系统"自作聪明"的自动提升优先级时, 可能会打乱我们的执行顺序
SetProcessPriorityBoost
可以设置是否允许系统对进程进行提升优先级
参数为TRUE时, 不允许提升参数为FALSE时, 允许提升
SetThreadPriorityBoost
可以设置是否允许系统对线程进行提升优先级
参数为TRUE时, 不允许提升参数为FALSE时, 允许提升
我们在看看不让自动提升的时候的情况:
#include <Windows.h>
#include <process.h>
#include <cstdio>
ULONG64 g_nNum1 = 0, g_nNum2 = 0, g_nNum3 = 0;
UINT WINAPI ThreadFun1(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum1;
}
return 0;
}
UINT WINAPI ThreadFun2(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum2;
}
return 0;
}
UINT WINAPI ThreadFun3(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum3;
}
return 0;
}
INT main()
{
HANDLE hThreads[3] = { INVALID_HANDLE_VALUE };
hThreads[0] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun1, (LPVOID)1, CREATE_SUSPENDED, NULL));
SetThreadPriorityBoost(hThreads[0], TRUE);
SetThreadPriority(hThreads[0], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[0]);
hThreads[1] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun2, (LPVOID)2, CREATE_SUSPENDED, NULL));
SetThreadPriorityBoost(hThreads[1], TRUE);
SetThreadPriority(hThreads[1], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[1]);
hThreads[2] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun3, (LPVOID)3, CREATE_SUSPENDED, NULL));
SetThreadPriorityBoost(hThreads[2], TRUE);
SetThreadPriority(hThreads[2], THREAD_PRIORITY_IDLE);
ResumeThread(hThreads[2]);
INT nExecTimes = 0;
while (nExecTimes++ < 10)
{
WaitForMultipleObjects(3, hThreads, TRUE, 1000);
// 此例子就不在做线程同步了
printf("g_Num1:[%lld]\ng_Num2:[%lld]\ng_Num3:[%lld]\n---------\n", g_nNum1, g_nNum2, g_nNum3);
}
for (INT i = 0; i < 3; ++i)
{
CloseHandle(hThreads[i]);
}
return 0;
}
可以看到, 我们的线程也是会被饿死的(注意观察输出的值的变化)
当然这个结果不是绝对的, 根据CPU的核心和系统性能而变化的..
这是为什么呢?
这是因为, 在多核CPU中, 线程饥饿的情况会得到缓解
它会自动调度饥饿线程去限制状态的CPU核心去运行
设置线程运CPU核心
SetProcessAffinityMask
- 它可以设置进程运行的CPU核心
- 参数1是
进程句柄 参数2是CPU运行的核心, 使用2进制表示的数
- 00000001 // 1号CPU
- 00000010 // 2号CPU
- 00000100 // 3号CPU
- 00001000 // 4号CPU
- 00000011 // 1号和2号CPU
- .... // 以此类推
SetThreadAffinityMask
- 它可以设置
线程运行的CPU核心 - 参数同SetProcessAffinityMask
GetSystemInfo
- 它可以获取系统信息, 包括CPU核心数
它的参数是一个SYSTEM_INFO的指针
- dwActiveProcessorMask // 二进制表示的CPU核心数
- dwNumberOfProcessors // 十进制表示的CPU核心数
- 可以使用 dwActiveProcessorMask 来&一个二进制的数来方便快捷的设置CPU运行核心

我们限制进程在单个CPU中运行:
#include <Windows.h>
#include <process.h>
#include <cstdio>
ULONG64 g_nNum1 = 0, g_nNum2 = 0, g_nNum3 = 0;
UINT WINAPI ThreadFun1(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum1;
}
return 0;
}
UINT WINAPI ThreadFun2(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum2;
}
return 0;
}
UINT WINAPI ThreadFun3(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum3;
}
return 0;
}
INT main()
{
SetProcessAffinityMask(GetCurrentProcess(), 0x1);
HANDLE hThreads[3] = { INVALID_HANDLE_VALUE };
hThreads[0] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun1, (LPVOID)1, CREATE_SUSPENDED, NULL));
SetThreadPriorityBoost(hThreads[0], TRUE);
SetThreadPriority(hThreads[0], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[0]);
hThreads[1] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun2, (LPVOID)2, CREATE_SUSPENDED, NULL));
SetThreadPriorityBoost(hThreads[1], TRUE);
SetThreadPriority(hThreads[1], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[1]);
hThreads[2] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun3, (LPVOID)3, CREATE_SUSPENDED, NULL));
SetThreadPriorityBoost(hThreads[2], TRUE);
SetThreadPriority(hThreads[2], THREAD_PRIORITY_IDLE);
ResumeThread(hThreads[2]);
INT nExecTimes = 0;
while (nExecTimes++ < 10)
{
WaitForMultipleObjects(3, hThreads, TRUE, 1000);
// 此例子就不在做线程同步了
printf("g_Num1:[%lld]\ng_Num2:[%lld]\ng_Num3:[%lld]\n---------\n", g_nNum1, g_nNum2, g_nNum3);
}
for (INT i = 0; i < 3; ++i)
{
CloseHandle(hThreads[i]);
}
return 0;
}
当然, 换一个更快的电脑, 会有更加明显的效果
高优先级线程打断低优先级线程
根据上边的例子, 我们在ThreadFun3上下断点
通过断点调试的方式, 可以看到线程抢到时间片的情况
同时, 你也会观察到, 虽然抢到了CPU时间片, 但是g_Num3并没有被++
这就是高优先级线程打断低优先级线程执行的情况
多线程注意事项
- 线程需要同步的话, 建议让线程在
同一个CPU核心(核心组)中运行 - 多线程需要注意高优先级线程打断低优先级线程的情况
- 需要注意系统自动提升线程优先级的情况
- 需要注意多核CPU下的线程运行情况
我们最后一个例子, 让每个线程都在一个CPU核心中执行
#include <Windows.h>
#include <process.h>
#include <cstdio>
ULONG64 g_nNum1 = 0, g_nNum2 = 0, g_nNum3 = 0;
UINT WINAPI ThreadFun1(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum1;
}
return 0;
}
UINT WINAPI ThreadFun2(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum2;
}
return 0;
}
UINT WINAPI ThreadFun3(LPVOID lParam)
{
while (TRUE)
{
//printf("%d\n", (INT)lParam);
++g_nNum3;
}
return 0;
}
INT main()
{
SetProcessAffinityMask(GetCurrentProcess(), 0x1);
HANDLE hThreads[3] = { INVALID_HANDLE_VALUE };
hThreads[0] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun1, (LPVOID)1, CREATE_SUSPENDED, NULL));
SetThreadAffinityMask(hThreads[0], 0x2);
SetThreadPriorityBoost(hThreads[0], TRUE);
SetThreadPriority(hThreads[0], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[0]);
hThreads[1] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun2, (LPVOID)2, CREATE_SUSPENDED, NULL));
SetThreadAffinityMask(hThreads[1], 0x4);
SetThreadPriorityBoost(hThreads[1], TRUE);
SetThreadPriority(hThreads[1], THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(hThreads[1]);
hThreads[2] = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadFun3, (LPVOID)3, CREATE_SUSPENDED, NULL));
SetThreadAffinityMask(hThreads[2], 0x8);
SetThreadPriorityBoost(hThreads[2], TRUE);
SetThreadPriority(hThreads[2], THREAD_PRIORITY_IDLE);
ResumeThread(hThreads[2]);
INT nExecTimes = 0;
while (nExecTimes++ < 10)
{
WaitForMultipleObjects(3, hThreads, TRUE, 1000);
// 此例子就不在做线程同步了
printf("g_Num1:[%lld]\ng_Num2:[%lld]\ng_Num3:[%lld]\n---------\n", g_nNum1, g_nNum2, g_nNum3);
}
for (INT i = 0; i < 3; ++i)
{
CloseHandle(hThreads[i]);
}
return 0;
}执行效果我就不放了, 大家可以自己去执行下试试
提醒一下, 需要有4核CPU的支持
要是真4核哦, 不是伪4核
未完待续...
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-06-25 at 07:10 am