【问题标题】:C# Can Barrier or something similar be used to synchonise tasks multiple times?C# 可以使用 Barrier 或类似的东西来多次同步任务吗?
【发布时间】:2018-09-10 23:13:15
【问题描述】:

我了解障碍可用于在第二阶段运行之前让多个任务同步完成。

我想让几个任务同步多个步骤,如下所示:

state is 1;
Task1 runs and pauses waiting for state to become 2;
Task2 runs and pauses waiting for state to become 2;
Task2 is final Task and causes the state to progress to state 2;
Task1 runs and pauses waiting for state to become 3;
Task2 runs and pauses waiting for state to become 3;
Task2 is final Task and causes the state to progress to state 3;
state 3 is final state and so all tasks exit.

我知道我可以在每个状态结束时启动新任务,但由于每个任务不会花费太长时间,我想避免为每个步骤创建新任务。

我可以使用 for 循环同步运行上述内容,但最终状态可能是 100,000,因此我希望使用多个线程来更快地运行该进程,因为该进程受 CPU 限制。

我尝试使用计数器来跟踪每个任务在完成时增加的已完成任务的数量。如果任务是要完成的最终任务,那么它将状态更改为下一个状态。所有已完成的任务然后使用while (iterationState == state) await Task.Yield 等待,但性能很糟糕,在我看来这是一种非常粗暴的做法。

完成上述工作的最有效方法是什么?必须有一个优化的工具来完成这项工作?

我正在使用Parallel.For,创建 300 个任务,每个任务需要运行多达 100,000 个状态。通过一个状态运行的每个任务在不到一秒的时间内完成,创建 300 * 100,000 个任务是一个巨大的开销,即使使用单个线程,同步运行整个任务也会更快。

所以我想创建 300 个任务并让这些任务在 100,000 个状态中同步移动。希望仅创建 300 个任务而不是 300 * 100,000 个任务的开销,以及任务之间优化同步的开销,将比在单个线程上同步执行时运行得更快。

每个状态必须完全完成才能运行下一个状态。

那么 - 这种情况下的最佳同步技术是什么?谢谢!

【问题讨论】:

  • 我理解正确吗 - 您一次只能运行两个任务,并且为了移动到下一个状态,您必须等待两个都完成并且您可能有多达 100,000 个状态?
  • 听起来你需要 Monitor.Pulse 和 Monitor.Wait
  • 可以切换到dataflow库吗?
  • @IanMercer 这是问题所在 - 大多数执行时间不到一毫秒。有些需要数百毫秒。所以我的并行化代码对于绝大多数执行来说确实是在浪费时间,但是在较长的执行期间让 CPU 以 5% 的速度运行是一种资源浪费——我正在尝试找到一种有效管理那些 1ms 在设计时是未知的。
  • 我认为@IanMercer 是正确的,他的解决方案比我的更干净。 Parallel.For 应该是最佳解决方案,并且具有基于它所运行的本地机器的“自动调整”本身的好处,而使用我的解决方案,您必须找出您移动到的每台机器的最佳线程数。话虽如此,但没有看到您尝试并行化的代码,这实际上只是提供解决方案的猜谜游戏。

标签: c#


【解决方案1】:

while (iterationState == state) await Task.Yield 确实是在 300 个任务之间同步的糟糕解决方案(不,300 个不一定非常昂贵:您只会分配合理数量的线程)。

这里的关键问题不是Parallel.For,它是在 300 个任务之间同步以高效地等待,直到每个任务都完成给定阶段。

这里最简单和最干净的解决方案可能是在阶段上有一个 for 循环,在你想要并行化的位上有一个 parallel.for:

for (int stage = 0; stage < 10000; stage++)
{
   // the next line blocks until all 300 have completed
   // will use thread pool threads as necessary
   Parallel.For( ... 300 items to process this stage ... );
}

无需额外的同步原语,无需消耗 CPU 的自旋等待,无需在线程之间进行不必要的颠簸以查看它们是否已准备好继续进行。

【讨论】:

  • 我已经尝试过了,性能比在 for 循环中使用 for 循环慢得多。我的结论是创建 300 * 100,000 个任务的开销与跨并行线程划分处理的性能提升相比太昂贵了。
  • @Ibraheem Parallel.For 不会“创建任务”,请参阅 stackoverflow.com/questions/5009181/…
  • @Ibraheem 你试过没有任何讨厌的“while ... Task.Yield”的东西?也许您应该发布您正在尝试改进的实际代码。
  • 是的,这是正确的,这是我尝试的第一件事。我正在使用Parallel.For 来“并行化”项目的其他区域。我讨厌的同步代码是我尝试使用长时间运行的任务而不是Parallel.For
【解决方案2】:

我想我理解你想要做什么,所以这里有一个建议的方法来处理它。注意 - 我使用 Action 作为阻塞集合的类型,但您可以将其更改为最适合您的场景的类型。

// Shared variables
CountdownEvent workItemsCompleted = new CountdownEvent(300);
BlockingCollection<Action> workItems = new BlockingCollection<Action>();
CancellationTokenSource cancelSource = new CancellationTokenSource();

// Work Item Queue Thread
for(int i=1; i < stages; ++i)
{
    workItemsCompleted.Reset(300);
    for(int j=0; j < workItemsForStage[i].Count; ++j)
    {
         workItems.Add(() => {}) // Add your work item here
    }
    workItemsCompleted.Wait(token) // token should be passed in from cancelSource.Token
}

// Worker threads that are making use of the queue
// token should be passed to the threads from cancelSource.Token
while(!token.IsCancelled)
{
    var item = workItems.Take(token); // Blocks until available item or token is cancelled
    item();
    workItemsCompleted.Signal();
}

如果需要,您可以在主线程中使用cancelSource 来取消正在运行的操作。在您的工作线程中,您需要处理OperationCancelledException。使用此设置,您可以根据需要启动尽可能多的工作线程,并轻松对获得最佳性能的地方进行基准测试(可能只使用 10 个工作线程等)。只需启动尽可能多的工作人员,然后在工作项队列线程中排队工作项。它基本上是一种生产者-消费者类型的模型,只是生产者将一个阶段的工作排队,然后阻塞,直到该阶段完成,然后再排队下一轮的工作。

【讨论】:

  • 我没有想到要使用 BlockingCollection,我会试试这个解决方案,看看它对性能有何影响
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-18
  • 2021-11-04
  • 1970-01-01
  • 2016-03-08
相关资源
最近更新 更多