让许多工作线程等待主线程的最佳方法,反之亦然

时间:2016-04-21 13:23:34

标签: c# multithreading

我正在寻找一种快速方法让许多工作线程等待事件继续并阻塞主线程直到所有工作线程都完成。我首先使用了TPL或AutoResetEvent,但由于我的计算费用不高,所以开销太大了。

我发现了一个非常有趣的article关于这个问题,并且使用最后一个同步解决方案(Interlocked.CompareExchange)获得了很好的结果(仅使用一个工作线程)。但我不知道如何利用它来解决许多线程反复等待一个主要步骤的情况。

以下是使用单线程,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();
}

平均结果(以秒为单位)为:

单线程:0,002 CompareExchange:0,4 障碍:1,7

正如你所看到的障碍&#39;开销似乎高出4倍!如果有人可以重建我的CompareExchange方案来处理多个工作线程,这肯定会有所帮助!

当然,一百万次计算的1秒开销相当少!实际上它只是让我感兴趣。

编辑:

System.Threading.Barrier似乎是这种情况下最快的解决方案。为了保存双重阻止(所有工作准备工作,所有工作都已完成)我使用以下代码获得最佳结果:

while(work)
{
    while (barrier.ParticipantsRemaining > 1) { Thread.Sleep(0); }
    //Set work package
    barrier.SignalAndWait()
}

1 个答案:

答案 0 :(得分:0)

您似乎可能希望使用Barrier将多个工作人员与主要线程同步。

这是一个可编辑的例子。玩弄它,注意输出告诉你什么时候可以&#34;按&lt; Return&gt;向工人发出启动信号&#34;。

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();
    }
}