如何确保一个线程恰好在特定数量的其他线程运行结束后运行?

发布于 2024-11-15 06:19:09 字数 636 浏览 1 评论 0原文

我在 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()”的所有线程结束后运行。

I have a class in C# like this:

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

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

When I call the "Start()" method, an external class start to work and will create many parallel threads that those parallel threads call the "Method_01()" and "Method_02()" form above class. after end of working of the external class, the "Method_03()" will be run in another parallel thread.

Threads of "Method_01()" or "Method_02()" are created before creation of thread of Method_03(), but there is no guaranty to end before start of thread of "Method_03()". I mean the "Method_01()" or the "Method_02()" will lost their CPU turn and the "Method_03" will get the CPU turn and will end completely.

In the "Start()" method I know the total number of threads that are supposed to create and run "Method_01" and "Method_02()". The question is that I'm searching for a way using semaphore or mutex to ensure that the first statement of "Method_03()" will be run exactly after end of all threads which are running "Method_01()" or "Method_02()".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

木槿暧夏七纪年 2024-11-22 06:19:09

我想到的三个选项是:

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

我更喜欢使用 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.
  }
}

Three options that come to mind are:

  • Keep an array of Thread instances and call Join on all of them from Method_03.
  • Use a single CountdownEvent instance and call Wait from Method_03.
  • Allocate one ManualResetEvent for each Method_01 or Method_02 call and call WaitHandle.WaitAll on all of them from Method_03 (this is not very scalable).

I prefer to use a CountdownEvent because it is a lot more versatile and is still super scalable.

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.
  }
}
从﹋此江山别 2024-11-22 06:19:09

这对于 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();

This appears to be a perfect job for Tasks. Below I assume that Method01 and Method02 are allowed to run concurrently with no specific order of invocation or finishing (with no guarantee, just typed in out of memory without testing):

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();
转角预定愛 2024-11-22 06:19:09

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

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

What it sounds like you need to do is to create a ManualResetEvent (initialized to unset) or some other WatHandle for each of Method_01 and Method_02, and then have Method_03's thread use WaitHandle.WaitAll on the set of handles.

Alternatively, if you can reference the Thread variables used to run Method_01 and Method_02, you could have Method_03's thread use Thread.Join to wait on both. This assumes however that those threads are actually terminated when they complete execution of Method_01 and Method_02- if they are not, you need to resort to the first solution I mention.

心凉怎暖 2024-11-22 06:19:09

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

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

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

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

我是否正确理解了问题?

Why not use a static variable static volatile int threadRuns, which is initialized with the number threads Method_01 and Method_02 will be run.
Then you modify each of those two methods to decrement threadRuns just before exit:

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

Then in the beginning of Method_03 you wait until threadRuns is 0 and then proceed:

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

Did I understand the quesiton correctly?

妞丶爷亲个 2024-11-22 06:19:09

事实上,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.                
        }
    }

There is actually an alternative in the Barrier class that is new in .Net 4.0. This simplifies the how you can do the signalling across multiple threads.

You could do something like the following code, but this is mostly useful when synchronizing different processing threads.

 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.
        }
    }

I would also like to suggest that since your problem statement was quite abstract, that often actual problems that are solved using countdownevent are now better solved using the new Parallel or PLINQ capabilities. If you were actually processing a collection or something in your code, you might have something like the following.

 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.                
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文