【问题标题】:Reduce Context Switches Between Threads With Same Priority减少具有相同优先级的线程之间的上下文切换
【发布时间】:2014-10-24 19:45:41
【问题描述】:

我正在编写一个使用第三方库来执行繁重计算的应用程序。

这个库在内部实现并行性并产生给定数量的线程。我想运行这个库的几个(动态计数)实例,因此最终会严重超额订阅 cpu。

有什么方法可以增加进程中所有线程的“时间量子”,例如所有具有正常优先级的线程很少上下文切换(yield),除非它们通过例如明确地让出。信号量?

这样我可以避免过度订阅 CPU 的大部分性能开销。请注意,在这种情况下,我不在乎线程是否饿了几秒钟。

编辑:

一种复杂的方法是手动执行线程调度。

  1. 枚举具有特定优先级(例如正常)的所有线程。
  2. 暂停所有这些。
  3. 创建一个循环来恢复/暂停线程,例如40 毫秒,并确保运行的线程数不超过当前 cpu 计数。

这种方法有什么主要缺点吗?不确定恢复/暂停线程的开销是多少?

【问题讨论】:

  • lib 实例是否相互依赖?如果不是,为什么不能通过以线程池的方式运行与内核一样多的线程来避免可用内核过载?
  • 好吧,因为实例的数量是动态变化的,这很困难。我需要多次初始化/重新初始化库以根据当前负载更改线程数。
  • 如果你使用了线程池,任何时候运行的库实例的数量将与池中的线程数相同。其他库实例只会排队等待池线程可用于处理它们。如果结果超载,将 CPU 应用于实例似乎没有意义?
  • 如果是windows,也许您可​​以通过使用timeBeginPeriod() 和timeEndPeriod() 将ticker 设置为更高的值来降低tick rate。我认为默认是 15.625 毫秒(64 赫兹)。
  • 如果你有 8 个核心,启动 8 个线程。 8 个线程从阻塞队列中弹出库实例,并在它们可用时对其进行处理。如果排队的实例超过 8 个,则处理 8 个,其余的排队。当一个线程完成运行一个实例时,它会循环回来尝试从队列中弹出另一个实例。由于总是只有 8 个线程,因此无论有多少实例提交到队列,都不会出现过载。

标签: c++ c multithreading winapi context-switch


【解决方案1】:

您无需做任何特别的事情。任何体面的调度程序都不允许非强制上下文切换消耗大量 CPU 资源。不应使用任何没有像样调度程序的操作系统。

超额订阅 CPU 的性能开销不是非强制上下文切换的成本。为什么?因为调度程序可以简单地避免这些。调度程序仅在有好处时才执行非强制上下文切换。性能成本是:

  1. 完成一项作业可能需要更长的时间,因为在作业开始和作业结束之间,其他作业将完成更多的工作。

  2. 其他线程为其堆栈和相关的其他跟踪信息消耗内存。

  3. 更多的线程通常意味着更多的争用(例如,在分配内存时),这可能意味着更多的强制上下文切换,其中一个线程必须被切换出来,因为它无法向前推进。

当您知道调度程序不知道的重要事情时,您只想尝试更改调度程序的行为。这里没有这样的事情发生。所以默认行为就是你想要的。

【讨论】:

  • 嗯.. 这有点取决于可能需要重新加载到核心缓存中的数据量。我们不知道 OP 帖子中可能有多少:(
  • 这可能是因为您有很多强制上下文切换。调度程序无法运行尚未准备好运行的线程。非强制上下文切换是自行决定的。调度程序仅在认为有好处时才制作它们。只有在出于某种原因您比调度程序更了解时,您才应该尝试更改此行为。这里没有这样的原因。
  • 好答案。但是,据我所知,操作系统可能会经常强制上下文切换,以避免线程饥饿。我想避免的正是这些强制上下文切换。
  • @ronag - 不,它不会那样做。它必须更改运行线程集的唯一机会是当它被系统调用或硬件中断中断时。
  • 这可能是由于计时器的硬件中断而发生的。
【解决方案2】:

这种方法有什么主要缺点吗?不知道开销是多少 恢复/暂停线程是?

是的,恢复/暂停线程是在程序的用户模式中完成的非常危险的活动。所以它不应该被使用(几乎从不)。此外,我们不应该使用这些概念来实现任何现代调度程序为我们所做的事情。这个问题的其他帖子中也提到了这一点。

以上内容适用于任何操作系统,但从 SO post 标签看来,我认为它已被要求用于基于 Microsoft Windows 的系统。现在,如果我们从 MSDN 阅读 SuspendThread(),我们会得到以下信息:

“此函数主要设计用于调试器。它不打算用于线程同步。在拥有同步对象(例如互斥锁或临界区)的线程上调用 SuspendThread , 如果调用线程尝试获取挂起线程拥有的同步对象,则可能导致死锁

所以考虑一下线程获取了一些资源的场景(隐式地,即不是代码的一部分..通过库或内核模式),如果我们挂起线程,这将导致与该进程的其他线程一样的神秘死锁情况将等待该特定资源。事实上,在我们的程序中(在任何时候)我们都不确定任何正在运行的线程会获取什么样的资源,挂起/恢复线程并不是一个好主意。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-21
    • 1970-01-01
    • 1970-01-01
    • 2015-02-27
    相关资源
    最近更新 更多