【问题标题】:How to ensure run of a thread exactly after end of running of a specifc number of other threads?如何确保在特定数量的其他线程运行结束后正好运行一个线程?
【发布时间】:2011-06-13 02:44:40
【问题描述】:

我有这样的 C# 类:

public MyClass
{
   public void Start() { ... }

   public void Method_01() { ... }
   public void Method_02() { ... }
   public void Method_03() { ... }
}

当我调用“Start()”方法时,一个外部类开始工作并将创建许多并行线程,这些并行线程调用类上方的“Method_01()”和“Method_02()”形式。外部类工作结束后,“Method_03()”将在另一个并行线程中运行。

“Method_01()”或“Method_02()”的线程是在Method_03()的线程创建之前创建的,但不能保证在“Method_03()”的线程开始之前结束。我的意思是“Method_01()”或“Method_02()”将失去它们的 CPU 轮次,而“Method_03”将获得 CPU 轮次并完全结束。

在“Start()”方法中,我知道应该创建和运行“Method_01”和“Method_02()”的线程总数。问题是我正在寻找一种使用信号量或互斥锁的方法,以确保“Method_03()”的第一条语句将在运行“Method_01()”或“Method_02()”的所有线程结束后准确运行.

【问题讨论】:

    标签: c# multithreading synchronization semaphore


    【解决方案1】:

    想到的三个选项是:

    • 保留Thread 实例的数组,并从Method_03 对所有这些实例调用Join
    • 使用单个CountdownEvent 实例并从Method_03 调用Wait
    • 为每个Method_01Method_02 调用分配一个ManualResetEvent,然后从Method_03 对所有它们调用WaitHandle.WaitAll(这不是很可扩展)。

    我更喜欢使用CountdownEvent,因为它用途广泛,而且仍然具有超强的可扩展性。

    public class MyClass
    {
      private CountdownEvent m_Finished = new CountdownEvent(0);
    
      public void Start()
      {
        m_Finished.AddCount(); // Increment to indicate that this thread is active.
    
        for (int i = 0; i < NUMBER_OF_THREADS; i++)
        {
          m_Finished.AddCount(); // Increment to indicate another active thread.
          new Thread(Method_01).Start();
        }
    
        for (int i = 0; i < NUMBER_OF_THREADS; i++)
        {
          m_Finished.AddCount(); // Increment to indicate another active thread.
          new Thread(Method_02).Start();
        }
    
        new Thread(Method_03).Start();
    
        m_Finished.Signal(); // Signal to indicate that this thread is done.
      }
    
      private void Method_01()
      {
        try
        {
          // Add your logic here.
        }
        finally
        {
          m_Finished.Signal(); // Signal to indicate that this thread is done.
        }
      }
    
      private void Method_02()
      {
        try
        {
          // Add your logic here.
        }
        finally
        {
          m_Finished.Signal(); // Signal to indicate that this thread is done.
        }
      }
    
      private void Method_03()
      {
        m_Finished.Wait(); // Wait for all signals.
        // Add your logic here.
      }
    }
    

    【讨论】:

    • 我没有看到对方法 3 的调用。在我假设您不需要启动方法中的 m_Finished.signal 而是调用启动 method_03 线程的问题下可能会令人困惑。
    • 没关系,我知道为什么你有 m_Finished 信号。
    • @Xenophile:不,你是对的,没有Method_03 的电话令人困惑。我加了。
    【解决方案2】:

    这似乎是Tasks 的完美工作。下面我假设Method01Method02 允许同时运行,没有特定的调用或完成顺序(没有保证,只是在没有测试的情况下输入了内存不足):

    int cTaskNumber01 = 3, cTaskNumber02 = 5;
    Task tMaster = new Task(() => {
        for (int tI = 0; tI < cTaskNumber01; ++tI)
            new Task(Method01, TaskCreationOptions.AttachedToParent).Start();
        for (int tI = 0; tI < cTaskNumber02; ++tI)
            new Task(Method02, TaskCreationOptions.AttachedToParent).Start();
    });
    // after master and its children are finished, Method03 is invoked
    tMaster.ContinueWith(Method03);
    // let it go...
    tMaster.Start();
    

    【讨论】:

      【解决方案3】:

      听起来您需要做的是为 Method_01 和 Method_02 中的每一个创建 ManualResetEvent(初始化为取消设置)或其他一些 WatHandle,然后让 Method_03 的线程在句柄集上使用 WaitHandle.WaitAll

      或者,如果您可以引用用于运行 Method_01 和 Method_02 的 Thread 变量,您可以让 Method_03 的线程使用 Thread.Join 来等待两者。然而,这假设这些线程在完成 Method_01 和 Method_02 的执行时实际上已终止——如果没有,您需要求助于我提到的第一个解决方案。

      【讨论】:

      • 只要所有执行Method_01和Method_02的线程都在Method_03的线程启动之前启动,那么无论有多少线程运行_01和_02,这两个都可以工作。这就是我阅读问题的方式——我错过了什么吗?
      • 另外请记住,对于我的第一个解决方案,它们不仅需要启动,还需要创建它们的 WaitHandles 并将它们添加到 Method_03 的线程可以等待的某个集合中。增量计数器解决方案也是如此。
      【解决方案4】:

      为什么不使用一个静态变量static volatile int threadRuns,它被初始化为线程Method_01 和Method_02 将运行。 然后你修改这两个方法中的每一个,在退出前递减threadRuns

      ...
      lock(typeof(MyClass)) {
          --threadRuns;
      }
      ...
      

      然后在 Method_03 的开头你等到 threadRuns 为 0 然后继续:

      while(threadRuns != 0)
         Thread.Sleep(10);
      

      我是否正确理解了问题?

      【讨论】:

      • 此解决方案有效,但可能会导致 CPU 使用率增加和延迟,具体取决于您在轮询之间等待的时间。此外,您还想使用 Interlocked.Decrement。 -- 不是线程安全的。
      • 是的。 Inrelocked.Decrement 当然会起作用,但就我而言,我只是锁定类,所以它也应该起作用。为了避免增加 CPU 使用率,可以在 Start() 中使用 ManualResetEvent,当它们递减时将在 _01 或 _02 内重置,_03 将等待它。
      【解决方案5】:

      Barrier 类中实际上有一个替代方案,它是 .Net 4.0 中新增的。这简化了您跨多个线程执行信号的方式。

      您可以执行以下代码,但这在同步不同的处理线程时最有用。

       public class Synchro
          {
              private Barrier _barrier;          
      
              public void Start(int numThreads)
              {
                  _barrier = new Barrier((numThreads * 2)+1);
                  for (int i = 0; i < numThreads; i++)
                  {
                      new Thread(Method1).Start();
                      new Thread(Method2).Start(); 
                  }
                  new Thread(Method3).Start();
              }
      
              public void Method1()
              {
                  //Do some work
                  _barrier.SignalAndWait();
              }
      
              public void Method2()
              {
                  //Do some other work.
                  _barrier.SignalAndWait();
              }
      
              public void Method3()
              {
                  _barrier.SignalAndWait();               
                  //Do some other cleanup work.
              }
          }
      

      我还想建议,由于您的问题陈述非常抽象,通常使用 countdownevent 解决的实际问题现在使用新的 Parallel 或 PLINQ 功能可以更好地解决。如果您实际上是在代码中处理集合或其他内容,则可能会出现以下内容。

       public class Synchro
          {
              public void Start(List<someClass> collection)
              {
                  new Thread(()=>Method3(collection));
              }
      
              public void Method1(someClass)
              {
                  //Do some work.               
              }
      
              public void Method2(someClass)
              {
                  //Do some other work.                
              }
      
              public void Method3(List<someClass> collection)
              {
                  //Do your work on each item in Parrallel threads.
                  Parallel.ForEach(collection, x => { Method1(x); Method2(x); });
                  //Do some work on the total collection like sorting or whatever.                
              }
          }
      

      【讨论】:

      • 我实际上会稍微不同地构造它,这样您就没有创建 Barrier 类的 start 方法,但这已经很好地展示了这个概念。
      • 感谢您向我介绍屏障类。如果我没有理解错误,那么在这种情况下,屏障类对我没有帮助,因为我不想在终点阻塞我的所有线程。一些我希望在最后一个线程结束时收到通知的方式。
      • Brian 的 CountDownEvent 示例可能是您最好的选择。我只是在展示另一种选择。
      猜你喜欢
      • 1970-01-01
      • 2021-06-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多