【问题标题】:Why threads starve even on preemptive multitasking OS (Windows 7)为什么即使在抢占式多任务操作系统(Windows 7)上线程也会饿死
【发布时间】:2012-08-10 04:27:06
【问题描述】:

我编写了一个 Win32 应用程序(在 Delphi-7 中,它是 32 位使用 TThread 类)来创建 100 个线程。每个线程在恢复时将连续(在循环中)增加与线程对象关联的 64 位计数器(因此不会锁定或共享数据)。

如果您让系统运行 10 到 15 秒然后停止,您应该会看到每个线程中的计数大致相同。但我观察到,81 个线程运行了 4 亿次以下的循环,其余的循环超过了 9.5 亿次。与最快的 21.11 亿相比,最慢的线程只有 2.3 亿。

根据 MSDN,抢占式多任务处理处于线程级别(而不是进程级别),因此我的每个线程都应该以循环方式获得其时间片。我在这里缺少什么,为什么会出现这种差异?

Edit1:机器配置:Intel i7 Quad Core 3.4GHz,超线程已开启(一次 8 个活动线程)。运行 Windows-7 64 位专业版(并且测试应用程序是 32 位)

Edit2(线程代码):测试应用程序是在启用优化且没有任何调试信息的情况下构建的。在 IDE 之外运行测试应用程序。

type

  TMyThread = class(TThread)
  protected
    FCount: Int64;
  public
    constructor Create;
    procedure Execute; override;
    property Count: Int64 read FCount;
  end;


{ TMyThread }

constructor TMyThread.Create;
begin
  inherited Create(True);
  FCount := 0;
end;  

procedure TMyThread.Execute;
begin
  inherited;
  while not Terminated do
  begin
    Inc(FCount);
  end;
end;

【问题讨论】:

  • 你没有说你的计数器是如何放置在内存中的;我猜如果计数器形成一个实心数组,当它们增加计数器时可能会导致线程相互依赖。
  • 您能否发布您的 TThread 派生类 dec。 +“执行”方法?我想试一试。如果您还没有尝试过,如果您在 IDE 之外运行应用程序会发生什么?另外,什么操作系统?
  • OK - W7 - 它在问题标题中这样说。 64 位还是 32 位?
  • @Serg - 你可能是对的,一些令人讨厌的错误共享与超出缓存线边界的线程集:(
  • 如果线程增量计数器在一致的基础上神奇地一致在 10% 以内,我会感到震惊。混沌是线程与 CPU 微架构特性(如缓存、分支预测、虚拟内存和交换)以及 IO-bound 和 CPU-bound 线程的动态 CPU 负载相结合的本质。这不是 1980 年的 8 位无缓存 1mhz 微型 CPU。

标签: multithreading delphi winapi delphi-7 multitasking


【解决方案1】:

这里有一些很好的推理..但是有一些功能需要考虑。 Windows 正在尝试使用软件进行多任务处理。 你的硬件不是多任务处理,它使用能力来做并行处理系统会做的事情。 在windows下,它优先。在很多方面......以及它令人困惑。

让我这样解释。 我有一个小程序可以监视我的核心以供使用。 当 Windows 加载时,您会认为所有内核都会被使用。没有。 随着 Windows 的加载,其他内核开始使用。 然后你会想,当 Windows 加载时,它会加速加载,因为它可以访问内核。它不会加速。它不使用核心是全速加载更快。 即使 Windows 在加载和运行时将程序推向 1 个核心,它也会等待它们完成。如果它使用所有内核来处理每个程序,它使用软件(大约比硬件慢 100 倍)来组装另一端的部件。 很久以前,英特尔想将硬件更改为并行处理,MS 说“不”,因为他们的软件不是为它设计的。现在他们正试图将基于串行的硬件设计推向 N 点。甚至在 MS 买了 NT 软件之后。最近,他们忘记了使用它的大部分设计。 需要进行一些硬件更改。需要编程语言更改(MS创建了编程语言)并且需要重新设计windows的核心。没有改变。它需要返回并从头开始。祝你好运。 告诉你这个想法有多古老...... VIVA La' Amiga。

【讨论】:

    【解决方案2】:

    我已经复制并确认了您的结果。此外,禁用线程优先级提升不会改变分布。 GetThreadTimes 报告说,具有较高 Values 的线程占用了更多 UserTime,反之亦然,而 KernelTime 似乎与 Values 没有相关性。

    Thread 97: 1081,5928 Ke:0 Us:25116161
    Thread 98: 1153,8029 Ke:0 Us:26988173
    Thread 99: 704,6996  Ke:0 Us:16848108
    

    显然,有些线程确实比其他线程运行得更频繁。

    我没有绘制结果图,但我想我们看到的是Normal distribution,这意味着结果取决于许多因素,其中一些是随机的。

    我尝试禁用超线程(这有点平滑结果),然后为每个线程分配一个物理处理器(通过使用SetThreadAffinityMask)。在第二种情况下,值彼此更接近。

    SetThreadAffinityMask(Self.Handle, 1 shl (FIndex mod 4));
    

    我可以理解为什么在超线程系统上运行会使某些线程“不走运”:它们被安排与同一物理处理器上的其他线程竞争,并且由于 "soft affinity" 到这个虚拟内核,它们得到了一次又一次地运行它,因此得分低于其他人。

    但至于为什么将每个线程绑定到一个固定内核有助于非超线程系统,我不知道。

    可能还涉及其他随机事物,例如其他进程在内核上的活动。如果与同一核心关联的其他进程的线程突然唤醒并开始做一些(相对)繁重的工作,线程可能会变得“不走运”。

    不过,这一切都是猜测。

    【讨论】:

    • 我对 32 位和 64 位可执行文件(来自相同的代码)做了更多实验,但结果相似。我还尝试通过在线程过程中的每个循环之后使用 Sleep(0) 来从线程中屈服,这会产生更奇怪的结果。即使对于 20 或 30 等较少数量的线程,行为也是相同的。
    【解决方案3】:

    Windows 7 专为用户 领域而设计。当你的第一个线程想要工作时,操作系统会给它一个时间片。毕竟,您,用户,才刚刚开始。当连续第 50 个线程(来自同一个进程!)想要工作时,更高优先级的线程(由 Windows 7 本身控制的后台进程)介入。这种情况发生的方式使得 some 线程幸运

    您和我并不真正想要一个根据用户登陆进程的突发奇想分配 CPU 时间的个人操作系统。我很想知道 2008 R2 服务器是如何处理这个问题的。您还可以尝试使用“高级”选项卡设置:“选择如何分配处理器资源”。

    【讨论】:

      【解决方案4】:

      循环调度对于内核来说是一种显而易见的策略。然而,这不是 Windows 调度程序的工作方式。在 Windows 9x 时代,它曾经是一个非常有能力为各种虚拟机提供同等时间的调度程序。但不是在 NT 分支中,由 Dave Cutler 的小组开始,调度纯粹基于 优先级

      具有最高优先级的线程获取 CPU。 Windows 中有另一段代码修改线程的优先级,从创建线程时获得的默认优先级修改它。该代码知道诸如拥有前台窗口的线程之类的东西。或者一个线程正在等待一个收到信号的同步对象。或者尝试解决优先级反转问题的更奇怪的调度问题。随机给线程一个运行的机会,即使它不是轮到它。

      首先专注于编写健全的代码。启动一百个线程并不是一件很明智的事情。您正在尝试消耗机器实际上没有可用的资源,没有人拥有具有一百个核心的机器。然而。 2 的幂,先买一台 128 核的机器。

      【讨论】:

      • 感谢您的回复和建议。由于我的程序都没有运行,我的机器上有 1105 个线程。不知道谁写的代码不健全。当您第一次启动 vanilla windows 7 专业版时,它会有多少个线程?
      • 这是我机器上线程利用率的细分(前 5 名):System (215)、Kaspersky AV (106)、SQLServer Express (49)、SVCHost(38+35)、Outlook (33)。我有两个程序作为服务运行,一个有 38 个线程,另一个有 27 个线程。有问题的程序是一个测试程序,当我注意到我的一些线程正在挨饿 CPU 时编写。
      • 所以你看到 none 的线程在做真正的工作。这是一个调度算法永远无法解决的问题,它只能给想要执行代码的线程时间。添加更多线程并不能解决这个问题。实际上,这使情况变得更糟。您的进程很有可能受 I/O 限制,等待 dbase 服务器。
      • 另外,就像 Hans 所说,1000 个线程等待 I/O 或彼此等待是正常的,100 个就绪/正在运行的线程充其量是在浪费堆栈空间,如果线程频繁访问 [(L1 cache size/threadsCount) 或更多] 数据量,(是的,在 OP 代码中不是这种情况),那么大量 CPU/内存带宽可能会浪费在缓存交换上。
      • 这里的圣言。也就是说,两位的权力并不完全正确。 AMD 一直在推出时髦的 6 核、12 核和 24 核机器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-11
      • 1970-01-01
      • 1970-01-01
      • 2014-04-25
      • 1970-01-01
      • 1970-01-01
      • 2013-05-21
      相关资源
      最近更新 更多