【问题标题】:For loop in many threads多线程中的for循环
【发布时间】:2011-08-20 06:15:37
【问题描述】:

如何在另一个线程中运行每个 for 循环调用,但 ExternalMethod 的延续应该等待 for 循环中最后一个工作线程的结束(并同步)?

ExternalMethod()
{
    //some calculations
    for (int i = 0; i < 10; i++)
    {
        SomeMethod(i);
    }
    //continuation ExternalMethod
}

【问题讨论】:

标签: c# multithreading .net-3.5 loops thread-safety


【解决方案1】:

一种方法是使用ManualResetEvent

考虑以下代码(请注意,这不应被视为工作示例,卡在 OSX 上,因此没有 VS 或 C# 编译器可以检查):

static ManualResetEvent mre = new ManualResetEvent(false);
static int DoneCount = 0;
static int DoneRequired = 9;
void ExternalMethod() {
        mre.Reset();
        for (int i = 0; i < 10; i++) {
                new Thread(new ThreadStart(ThreadVoid)).Start();
        }
        mre.WaitOne();
}

void ThreadVoid() {
        Interlocked.Increment(ref DoneCount);   

        if (DoneCount == DoneRequired) {
                mre.Set();
        }
}

重要提示 - 这可能不是最好的方法,只是使用 ManualResetEvent 的一个示例,它完全可以满足您的需求。

如果您使用的是 .NET 4.0,则可以使用 Parallel.For 循环 - explained here

【讨论】:

  • 作为旁注,您可以随时获取Mono:OSX
  • 您不应该使用Interlocked.Increment 而不是++ 吗?我虽然 ++ 不安全。
  • 这是非常正确的,但它是一台工作机器,没有正当理由这样做......然而;-)
  • @recursive 根据正在发生的操作,这无关紧要。然而,使用更安全的操作是始终的最佳实践。答案已更新!
  • re ++ vs Interlocked.Increment,因为共享的 DoneCount 存在争用,++ 总是错误的。它不依赖任何东西,++ 在这种情况下总是一个错误。
【解决方案2】:
System.Threading.Tasks.Parallel.For(0, 10, (i) => SomeMethod(i));

【讨论】:

  • 很好,但我需要 .NET 3.5 :)
【解决方案3】:

一种方法是使用CountdownEvent

ExternalMethod()
{
    //some calculations
    var finished = new CountdownEvent(1);
    for (int i = 0; i < 10; i++)
    {
        int capture = i; // This is needed to capture the loop variable correctly.
        finished.AddCount();
        ThreadPool.QueueUserWorkItem(
          (state) =>
          {
            try
            {
              SomeMethod(capture);
            }
            finally
            {
              finished.Signal();
            }
          }, null);
    }
    finished.Signal();
    finished.Wait();
    //continuation ExternalMethod
}

如果CountdownEvent 不可用,那么这是另一种方法。

ExternalMethod()
{
    //some calculations
    var finished = new ManualResetEvent(false);
    int pending = 1;
    for (int i = 0; i < 10; i++)
    {
        int capture = i; // This is needed to capture the loop variable correctly.
        Interlocked.Increment(ref pending);
        ThreadPool.QueueUserWorkItem(
          (state) =>
          {
            try
            {
              SomeMethod(capture);
            }
            finally
            {
              if (Interlocked.Decrement(ref pending) == 0) finished.Set();
            }
          }, null);
    }
    if (Interlocked.Decrement(ref pending) == 0) finished.Set();
    finished.WaitOne();
    //continuation ExternalMethod
}

请注意,在这两个示例中,for 循环本身都被视为并行工作项(毕竟它与其他工作项位于单独的线程上)以避免在第一个工作项时可能发生的非常微妙的竞争条件在下一个工作项排队之前发出事件信号。

【讨论】:

  • 首先适用于 .NET 4.0。 Second 无法编译,因为 ThreadPool.QueueUserWorkItem 中的“Delegate 'System.Threading.WaitCallback' 不采用 '0' arguments”。
  • 已修复。我还注意到在 lambda 表达式中捕获循环变量的另一个问题。
【解决方案4】:

对于 .NET 3.5,可能是这样的:

Thread[] threads = new Thread[10];

for (int x = 0; x < 10; x++)
{
    threads[x] = new Thread(new ParameterizedThreadStart(ThreadFun));
    threads[x].Start(x);
}

foreach (Thread thread in threads) thread.Join();

使用Join() 方法可能看起来违反直觉,但由于您实际上是在执行 WaitAll 类型的模式,因此执行连接的顺序无关紧要。

【讨论】:

  • 我想同时并行运行每个迭代。只有当前线程应该等待从 for 循环结束最后一个线程。如果我理解正确你的例子 - 每个线程都在等待更早的线程 - 在我的情况下浪费时间
  • thread.Join() 调用导致调用(父)线程阻塞并等待被调用(子)线程完成。没有一个子线程依赖于任何其他线程。您在线程上等待的顺序无关紧要,因为对已完成线程的 Join() 调用将立即返回,而对正在运行的线程的调用将阻塞,直到它们完成。这里的优点是您的子线程不必合作才能使该技术起作用。换句话说,这种技术可以用于任何你想并行运行的线程(未修改)。
猜你喜欢
  • 2013-11-07
  • 2023-03-03
  • 2013-02-24
  • 1970-01-01
  • 2021-02-23
  • 2011-02-13
  • 1970-01-01
  • 1970-01-01
  • 2022-07-07
相关资源
最近更新 更多