【问题标题】:Force TPL Tasks to run on a single core强制 TPL 任务在单核上运行
【发布时间】:2026-01-21 00:10:02
【问题描述】:

我有一个包含一些处理组件的 ETL 项目。单个组件是基于 BlockingCollection 的生产者-消费者。所有组件都通过 Task.Run 并行执行,等待项目从其他组件到达,处理它们并将结果放入它们的输出集合(想想管道)。所有组件都通过 Task.Run() 执行。

是否可以在不为进程设置处理器亲和性的情况下强制任务在单核上运行(我不希望它们占用 100% 的多核 CPU)(这似乎有点矫枉过正)?

请注意,我仍然希望任务以并行方式运行 - 仅在单个内核上。

【问题讨论】:

  • 处理器亲和性似乎是最好的选择。为什么你认为这是矫枉过正?
  • 如果我可以将其设置为只使用一个核心,那么亲和力会很棒。但据我所知(我在这里可能错了)你需要明确指定一个核心 - 所以如果你有更多这样的进程,你必须以某种方式在它们之间“协商”要使用哪些核心(如果我有 8 个核心可用并且两个这样的进程,我希望它们在不同的内核上)。
  • 您是否关心您的任务在哪个内核上执行,或者您是否关心您的任务消耗的 CPU 功率是否超过单核提供的 CPU 功率? Is this an XY problem? 此外,虽然单核可以提供多任务处理,但您不能在单核上并行执行任何操作。
  • 我不关心正在使用哪个内核 - 我只想限制进程 CPU 时间以最多占用一个内核。亲和力“迫使”我明确我需要哪个核心——我不想这样做。你是对的:单核/并行 - 我的意思是我想要多任务处理(线程上下文切换)。
  • 我认为这个问题的答案是不,不可能强制进程以您希望的方式在单个内核上运行

标签: c# .net task-parallel-library


【解决方案1】:

任务在线程上执行,操作系统决定它执行哪个内核。

我认为除了设置处理器关联之外没有其他方法。

请看这里:https://msdn.microsoft.com/en-us/library/system.diagnostics.processthread.processoraffinity.aspx

您确定在一个内核上并行运行它们会使您的性能受益吗?为什么您不想让进程在需要时使用 100% 的 CPU?操作系统仍将优先考虑它与其他进程,而不是 necceserily 允许这样做

如果您担心您的进程对其他操作系统进程造成压力,您也可以降低线程/进程优先级:

进程优先级:https://msdn.microsoft.com/en-us/library/system.diagnostics.process.priorityclass.aspx 线程优先级:https://msdn.microsoft.com/en-us/library/system.threading.thread.priority(v=vs.110).aspx

【讨论】:

  • 你可能是对的,我试图控制它太多。问题是该系统有两个模块 - 一个快速、高可用性的模块和一个缓慢的后台 ETL(我并不真正关心它的性能)。我只是不希望第二个模块以任何方式减慢第一个模块的速度。限制内核数量似乎是一个好主意。但我同意我可以让操作系统决定(并在慢速模块上将进程优先级降低到低于正常值)。话虽如此,如果这是执行此操作的唯一方法,我将不得不将这两个模块(快速和慢速模块)拆分为两个专用进程。
  • 我明白了,所以也许解决方案只是降低 ETL 模块的线程/进程优先级。这样,如果主程序正在运行,并且 ETL 启动,操作系统仍然会更喜欢它而不是 ETL,因为它的优先级较低。我用 MSDN 上这些属性的链接编辑了我的主要答案
  • 鉴于任务通过调度程序分配给线程 - 也可以只在线程上使用。答案错误。
  • 虽然自定义 TaskScheduler 是一个不错的解决方案,但它会强制所有任务在单个线程上运行,这将导致在一个内核上运行,但不会并行运行任何任务(尽管在多个线程上一个核心!=真正的并行性)-如果我们将所有内容都限制在一个线程中,那么这里所有任务和 TPL 的使用都是毫无意义的。我们可以让一个线程处理集合,仅此而已。在他的问题中 - Karol 希望能够在他的 ETL 进程上并行运行任务 - 为了不减慢主进程,我不认为在这里使用一个线程是明智的解决方案。
  • 实际上它会 - 如果你无知到替换默认的任务调度程序。如果您不这样做并在另一个任务调度程序上运行您的任务 - 那么它不会。有趣的是如何理解文档使事情成为可能? drdobbs.com/parallel/specialized-task-schedulers-in-net-4-par/… 有一些代码示例。
【解决方案2】:

是的,这是完全可能的。你只需要实现你自己的TaskScheduler

事实上,TaskSchduler 的 API 文档中的示例说明了如何准确地完成您想要的 - 他们实现了一个 LimitedConcurrencyLevelTaskScheduler,让您可以设置您想要使用的工作线程的数量。

API 文档的备注部分中的链接也很有价值。 Samples for Parallel Programming with the .NET Framework 4 项目包含大量替代线程调度程序,详细描述了here。它们可能会启发您思考安排这些任务的替代方法。

这里唯一的转折是你不能再使用Task.Run() 快捷方式——你需要通过一个TaskFactory instead

【讨论】:

  • 但这不会导致任务根本不并行运行吗?如果我们只有 1 个线程,我们保证只有一个核心,但也不会并行处理任务
  • @yonigozman 这是一个了不起的问题!我想过在我的回答中深入探讨这个问题,但担心我会破坏讨论。这里的问题是每个人都对“并行”这个词过于松散——我认为 Karol 追求的是并发性,而不是并行性。作为背景,迄今为止我听到的关于并行与并发的最佳讨论是 Rob Pike 的著名演讲 here(它是关于 Go,但非常相关,我衷心推荐给任何开发人员)。在他的词典中,你永远不会在一个核心上“并行”运行。
  • ...既然我们已经用 Pike 的术语进行了讨论,我认为这个问题实际上是关于并发性,而不是并行性:Karol 怎么会有一个核心 deal一次处理 2 件以上的事情,因为一个核心不能一次 2 件以上的事情(使用多个线程是一种选择,但在一个核心上你只会产生上下文切换开销)。所以这给我们带来了我的建议:只使用一个线程并尽可能快地执行你的任务。通过任务,您仍然可以获得“并发”执行(处理 w/ 2+ 东西)......(所以很多这是我自己的解释......你和卡罗尔的可能与我的不同!)
  • @yonigozman 是对的——这保证了一个线程/核心,但是需要完成一个任务才能开始另一个任务——我不希望这样的行为。
  • 好的,所以你需要一堆经常互相抢占的线程。这很好,但值得进一步了解您的需求。 (例如,一个线程在该内核上抢占另一个线程的依据是什么?IO 完成?)
【解决方案3】:

使用Task.Run() 时,您对作业的控制非常低,并且一切都是并行的,除非您使用自定义调度程序。

我建议使用Task Parallel Library (TPL),而不是这种技术解决方案,这可以被视为处理线程作业的更高层。

在 TPL 中,您可以选择块类型来处理您的数据,甚至可以在它们之间连接块,因此当一个项目刚刚完成处理时,结果可以在下一个 TPL 块中排队。

您可以使用ActionBlock<T>:您定义要为每个要处理的项目执行的代码,当数据可用于带有.Post() 的ActionBlock 时,它会自动处理...在平行下。但根据您的需要,您可以指定MaxDegreeOfParallelism=1

因此,使用此方法您无法控制执行代码的核心,但您可以确保所有项目都将按顺序处理,并且一次不会使用多个核心。

var workerBlock = new ActionBlock<int>(
      // Simulate work by suspending the current thread.
      millisecondsTimeout => Thread.Sleep(millisecondsTimeout),
      // Specify a maximum degree of parallelism.
      new ExecutionDataflowBlockOptions
      {
         MaxDegreeOfParallelism = 1
      });
// Source: https://docs.microsoft.com/fr-fr/dotnet/api/system.threading.tasks.dataflow.actionblock-1?view=netcore-3.1

你也可以看这个complete article about TPL,很有意思。

【讨论】:

  • 投票者可以解释原因吗?我的解决方案确保所有内容一次最多在 1 个内核上执行。我记得你最初的问题:“我不希望他们占用 100% 的多核 CPU”。谢谢。
最近更新 更多