【问题标题】:Windows thread scheduler is unfair?Windows 线程调度器不公平?
【发布时间】:2013-05-09 03:40:53
【问题描述】:

有时,当我运行这个简单的程序时

#include <Windows.h>

DWORD WINAPI ThreadStart(LPVOID)
{
    for (;;) { }
    return 0;
}

int _tmain()
{
    SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);

    SYSTEM_INFO si;
    GetSystemInfo(&si);
    for (DWORD i = si.dwNumberOfProcessors * 2; i > 0; i--)
    { CloseHandle(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL)); }

    Sleep(INFINITE);
}

我经常观察到不公平的线程调度,例如:

并不是每次运行都不公平,但是当它时,它在应用程序的整个生命周期中都保持不公平。

为什么会发生这种情况,避免这种情况的正确方法是什么?

【问题讨论】:

  • 你的CPU是什么模式?它实际上是四核,还是具有超线程的双核?
  • 另外,什么版本的 Windows。了解这一点也很重要。
  • @selbie:2 核 HT(Core i5,笔记本电脑),Windows 8 x64。
  • 使用线程和处理器关联函数(例如 SetThreadIdealProcessor、SetThreadAffinityMask、SetProcessAffinityMask)会影响什么吗?
  • @HarryJohnston: Process Hacker!

标签: c windows winapi cpu scheduler


【解决方案1】:

一些选项:


如果(且仅当)您运行的是 Windows 7/8 64 位(不是 32 位)或 Windows Server 2008 R2 或更高版本,您可以完全绕过系统调度程序并且自己处理好这件事,虽然这不是一件小事!

它是described on MSDN here,称为用户模式调度 (UMS)

下面来自 MSDN 的部分截图 - 这对你有用吗?

此外,在您的 BIOS 中禁用超线程(如果适用)可能是一个想法,因为对于 Windows 如何区分逻辑内核和物理内核存在一些争议。 See here for a discussion.

还有一些可用的功能,例如SetThreadAffinityMask() (MSDN here) 和SetThreadIdealProcessor(),虽然我个人觉得这有点偶然,但可能会有所帮助。更常见的情况是我损害了整体吞吐量而不是帮助它。

【讨论】:

  • 我不能真正禁用 HT,因为该选项不存在(我也不想......我也不明白为什么它会改变任何东西)。不幸的是,设置亲和力或理想处理器也不会改变任何东西。 UMS 似乎太过分了,我不确定它是否会有所帮助,但至少要提到它+1,听起来它可以工作。
【解决方案2】:

只要不属于您程序一部分的线程运行,您就会在多处理器系统上看到这一点。假设有一个系统任务启动运行几秒钟,扫描.NET 程序集或其他东西。它会在多个时间片内将您的一个线程从调度中剔除,而其他内核上的线程继续运行。被淘汰的线程永远赶不上其他线程。

【讨论】:

  • 这将永远持续下去。我不是在计算绝对周期,而是在计算 CPU 使用率,这与线程的过去历史无关。此外,没有系统任务等导致此问题 - 这是唯一正在运行的程序,显然很容易从任务管理器或类似的工具中分辨出来。
【解决方案3】:

这是在黑暗中猜测。

但我会建议超线程可能会稍微扭曲结果。

如果您的笔记本电脑的 bios/cmos 允许像某些计算机那样禁用超线程,那么看到您的代码仅在真实内核上运行的数字会很有趣。

即使HT是它偏向线程的原因,我仍然不知道它为什么会这样做。

【讨论】:

  • 不,我不能禁用 HT,正如你提到的,它并不能真正解释我得到了什么。
【解决方案4】:

在我的系统上,SetThreadAffinityMask 似乎可以缓解这个问题。仍然存在一些不平衡,显然是由于一个核心的可用时间少于另一个,但没有那么严重。

Windows 似乎不愿意在内核之间移动这些线程;他们很少在第一秒左右改变核心。如果线程在内核之间分布不均,则线程时间会反映这一点。

这是我用来尝试亲和掩码的代码:

#include <Windows.h>
#include <stdio.h>

DWORD WINAPI ThreadStart(LPVOID arg)
{
    DWORD pn1, pn2;

    printf("Thread %u on processor %u\n", GetThreadId(GetCurrentThread()), pn1 = GetCurrentProcessorNumber());

    // The problem comes back if you enable this line
    // SetThreadAffinityMask(GetCurrentThread(), -1);

    for (;;) 
    { 
        for (int i = 0; i < 10000; i++);
        pn2 = GetCurrentProcessorNumber();
        if (pn2 != pn1)
        {
            pn1 = pn2;
            printf("Thread %u on processor %u\n", GetThreadId(GetCurrentThread()), pn1);
        }
    }
    return 0;
}

int main(int argc, char ** argv)
{
    SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);

    SYSTEM_INFO si;
    GetSystemInfo(&si);

    for (DWORD i = 0; i < si.dwNumberOfProcessors; i++)
    { 
        SetThreadAffinityMask(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL), 1 << i);
        SetThreadAffinityMask(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL), 1 << i);  
    }

    Sleep(INFINITE);
    return 0;
}

这种方法似乎也可以缓解这个问题,尽管效果可能不那么好:

#include <Windows.h>
#include <stdio.h>

DWORD WINAPI ThreadStart(LPVOID arg)
{
    for (;;);
    return 0;
}

int main(int argc, char ** argv)
{
    SYSTEM_INFO si;
    GetSystemInfo(&si);

    for (DWORD i = si.dwNumberOfProcessors * 2; i > 0; i--)
    { 
        CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL);
    }

    for (;;)
    {
        SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
        Sleep(100);
        SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
    }

    return 0;
}

我怀疑我们正在研究某种节能措施,该措施是基于低优先级线程不需要公平调度的假设而设计的。 (可以说,将线程或进程设置为低优先级表示“我不在乎我得到多少 CPU。”)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-26
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 2012-04-23
    • 1970-01-01
    • 2017-03-24
    • 2018-09-08
    相关资源
    最近更新 更多