[英]Best way to let many worker-threads wait for mainthread and vice versa
I'm looking for a fast way to let many worker threads wait for an event to continue and block the main thread until all worker threads are finished. 我正在寻找一种快速方法让许多工作线程等待事件继续并阻塞主线程直到所有工作线程都完成。 I first used TPL or AutoResetEvent but since my calculation isn't that expensive the overhead was way too much. 我第一次使用TPL或AutoResetEvent,但由于我的计算并不昂贵,所以开销太大了。
I found a pretty interesting article concerning this problem and got great results (using only one worker thread) with the last synchronization solution (Interlocked.CompareExchange). 我发现了一篇关于这个问题的非常有趣的文章 ,并且使用最后一个同步解决方案(Interlocked.CompareExchange)获得了很好的结果(仅使用一个工作线程)。 But I don't know how to utilize it for a scenario where many threads wait for one main tread repeatedly. 但我不知道如何利用它来解决许多线程反复等待一个主要步骤的情况。
Here is an example using single thread, CompareExchange, and Barrier: 以下是使用单线程,CompareExchange和Barrier的示例:
static void Main(string[] args)
{
int cnt = 1000000;
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < cnt; i++) { }
Console.WriteLine($"Single thread: {stopwatch.Elapsed.TotalSeconds}s");
var run = true;
Task task;
stopwatch.Restart();
int interlock = 0;
task = Task.Run(() =>
{
while (run)
{
while (Interlocked.CompareExchange(ref interlock, 0, 1) != 1) { Thread.Sleep(0); }
interlock = 2;
}
Console.WriteLine($"CompareExchange synced: {stopwatch.Elapsed.TotalSeconds}s");
});
for (int i = 0; i < cnt; i++)
{
interlock = 1;
while (Interlocked.CompareExchange(ref interlock, 0, 2) != 2) { Thread.Sleep(0); }
}
run = false;
interlock = 1;
task.Wait();
run = true;
var barrier = new Barrier(2);
stopwatch.Restart();
task = Task.Run(() =>
{
while (run) { barrier.SignalAndWait(); }
Console.WriteLine($"Barrier synced: {stopwatch.Elapsed.TotalSeconds}s");
});
for (int i = 0; i < cnt; i++) { barrier.SignalAndWait(); }
Thread.Sleep(0);
run = false;
if (barrier.ParticipantsRemaining == 1) { barrier.SignalAndWait(); }
task.Wait();
Console.ReadKey();
}
Average results (in seconds) are: 平均结果(以秒为单位)是:
Single thread: 0,002 CompareExchange: 0,4 Barrier: 1,7 单线程:0,002比较交换:0,4障碍:1,7
As you can see Barriers' overhead seems to be arround 4 times higher! 正如你所看到的,障碍的开销似乎高出4倍! If someone can rebuild me the CompareExchange-scenario to work with multiple worker threads this would surely help, too! 如果有人可以重建我的CompareExchange方案来处理多个工作线程,这肯定也会有所帮助!
Sure, 1 second overhead for a million calculations is pretty less! 当然,一百万次计算的1秒开销相当少! Actually it just interests me. 其实它只是让我感兴趣。
Edit: 编辑:
System.Threading.Barrier seems to be the fastest solution for this scenario. System.Threading.Barrier似乎是这种情况下最快的解决方案。 For saving a double blocking (all workers ready for work, all workes finished) I used the following code for the best results: 为了保存双重阻止(所有工作人员都准备好工作,所有工作都已完成),我使用以下代码获得最佳结果:
while(work)
{
while (barrier.ParticipantsRemaining > 1) { Thread.Sleep(0); }
//Set work package
barrier.SignalAndWait()
}
It seems like you might want to use a Barrier
to synchronise a number of workers with a main thread. 您似乎可能希望使用Barrier
来将多个工作程序与主线程同步。
Here's a compilable example. 这是一个可编辑的例子。 Have a play with it, paying attention to when the output tells you that you can "Press <Return> to signal the workers to start". 玩弄它,注意当输出告诉你你可以“按<Return>指示工人开始”。
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
static class Program
{
static void Main()
{
print("Main thread is starting the workers.");
int numWorkers = 10;
var barrier = new Barrier(numWorkers + 1); // Workers + main (controlling) thread.
for (int i = 0; i < numWorkers; ++i)
{
int n = i; // Prevent modified closure.
Task.Run(() => worker(barrier, n));
}
while (true)
{
print("***************** Press <RETURN> to signal the workers to start");
Console.ReadLine();
print("Main thread is signalling all the workers to start.");
// This will wait for all the workers to issue their call to
// barrier.SignalAndWait() before it returns:
barrier.SignalAndWait();
// At this point, all workers AND the main thread are at the same point.
}
}
static void worker(Barrier barrier, int workerNumber)
{
int iter = 0;
while (true)
{
print($"Worker {workerNumber} on iteration {iter} is waiting for barrier.");
// This will wait for all the other workers AND the main thread
// to issue their call to barrier.SignalAndWait() before it returns:
barrier.SignalAndWait();
// At this point, all workers AND the main thread are at the same point.
int delay = randomDelayMilliseconds();
print($"Worker {workerNumber} got barrier, now sleeping for {delay}");
Thread.Sleep(delay);
print($"Worker {workerNumber} finished work for iteration {iter}.");
}
}
static void print(string message)
{
Console.WriteLine($"[{sw.ElapsedMilliseconds:00000}] {message}");
}
static int randomDelayMilliseconds()
{
lock (rng)
{
return rng.Next(10000) + 5000;
}
}
static Random rng = new Random();
static Stopwatch sw = Stopwatch.StartNew();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.