PoEdu培训 Windows班 第三十课 Windows 线程(十一) 线程优先级
文章类别: 培训笔记 0 评论

PoEdu培训 Windows班 第三十课 Windows 线程(十一) 线程优先级

文章类别: 培训笔记 0 评论

Windows 线程(十一) 线程优先级

线程优先级

在单核的条件下, 我们分析一下线程的优先级

线程优先级的级别

线程的可调度状态

通过以上描述, 我们发现貌似优先级较低的线程都会永远得不到执行机会
其实不是, 系统中的大部分线程都是属于不可调度状态的

// CPU瞬间100%的代码
#include <Windows.h>
#include <cstdio>

int main()
{
    // 设置进程在1号CPU中执行 也就是限制单核
    SetProcessAffinityMask(GetCurrentProcess(), 0x1);
    while(true)
    {
        // 一个空的循环
        // 放开printf CPU反而不会100%
        // printf("1");
    }
}

以上代码的原理就是, 空while会导致线程一直处于可调度状态
而调用了API后的线程会进行等待, 从而进入不可调度状态

线程优先级注意事项

需要注意的是, 优先级较高的线程总是会抢占优先级较低的线程的时间片

我们不可以自己设置线程优先级为0

进程优先级

我们都知道, 进程只是提供了代码和数据, 具体的执行还是靠线程
但是为什么会有进程优先级这种东西呢?
因为Windows需要不听的进行进程切换, 或者选择某一进程进行运行
所以Windows对进程提供了优先级的设置

进程优先级分为如下级别

不应该有任何的进程运行在实时优先级

进程优先级和线程优先级的关系

进程的优先级将会影响线程中的优先级
下面一个表格列举它们之间的关系
<!--

线程优先级real-time(实时)high(立即/高)above normal(较高/高于正常)normal(正常)below normal(较低/低于正常)idle(低)
time-critical311515151515
highest2615121086
above normal251411975
normal241310864
below normal23129753
lowest22118642
idle1611111

-->

进程优先级
线程优先级 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
比其他进程优先级下的最高线程优先级都高
所以,不要出现运行于实时优先级下的进程

优先级相关API

特别值得注意的是, CreateThread无法设置线程优先级, 线程默认是以normal优先级运行的

操作系统的动态提升优先级

因为线程是抢占式执行的
线程的优先级越高, 就能越多的抢占CPU时间片
这样就造成了一些低优先级的进程饥饿的情况
还有, 就是在系统进行IO操作的时候, 可能会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)
    {
        ++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;
}

Alt 执行结果

当系统"自作聪明"的自动提升优先级时, 可能会打乱我们的执行顺序

SetProcessPriorityBoost
可以设置是否允许系统对进程进行提升优先级

SetThreadPriorityBoost
可以设置是否允许系统对线程进行提升优先级

我们在看看不让自动提升的时候的情况:

#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;
}

Alt 执行结果

可以看到, 我们的线程也是会被饿死的(注意观察输出的值的变化)
当然这个结果不是绝对的, 根据CPU的核心和系统性能而变化的..
这是为什么呢?
这是因为, 在多核CPU中, 线程饥饿的情况会得到缓解
它会自动调度饥饿线程去限制状态的CPU核心去运行

设置线程运CPU核心

SetProcessAffinityMask

SetThreadAffinityMask

GetSystemInfo

Alt 执行结果

我们限制进程在单个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;
}

Alt 执行结果

当然, 换一个更快的电脑, 会有更加明显的效果

高优先级线程打断低优先级线程

根据上边的例子, 我们在ThreadFun3上下断点

通过断点调试的方式, 可以看到线程抢到时间片的情况
同时, 你也会观察到, 虽然抢到了CPU时间片, 但是g_Num3并没有被++
这就是高优先级线程打断低优先级线程执行的情况

多线程注意事项

我们最后一个例子, 让每个线程都在一个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核

未完待续...

如有错误,请提出指正!谢谢.

回复