【问题标题】:Iterator in a Parallel.ForEach is run on multiple threadsParallel.ForEach 中的迭代器在多个线程上运行
【发布时间】:2016-08-31 15:35:25
【问题描述】:

我有一些在Parallel.ForEach 循环中运行的代码。循环中的代码是线程安全的,但我使用的迭代器(带有yield return 的自定义方法)不是。似乎迭代器正在多个线程上运行,这可能会导致问题。

问题的背景

迭代器包含对 NHibernate 的调用(尽管稍后您将看到这完全是偶然的),并且由于我在并行化代码时遇到问题,我在 NHibernate Profiler 中查看了是否可以对这种情况有所了解。通过它开始报告“在多个线程中使用单个会话可能是一个错误”。

现在,根据 NHibernate Profiler 的说法,有问题的代码位于迭代器内部(因此它并没有试图在 Parallel.ForEach 操作中的其他地方实现某些东西)。所以我添加了一些我自己的代码,这样我就可以检测到 NHibernate Profiler 是什么,我看到了同样的东西。

迭代器方法是从多个线程调用的——我认为这是不可能的,正如其他人似乎也认为的那样,例如这个其他的答案:https://stackoverflow.com/a/26553810/8152

问题的简化演示

为了演示这个问题(不需要我所有无关的 gubbins 处理 NHibernate 等),我编写了一个简单的控制台应用程序来显示相同​​的问题:

public class Program
{
    public static void Main(string[] args)
    {
        Parallel.ForEach(YieldedNumbers(), (n) => { Thread.Sleep(n); });
        Console.WriteLine("Done!");
        Console.ReadLine();
    }

    public static IEnumerable<int> YieldedNumbers()
    {
        Random rnd = new Random();
        int lastKnownThread = Thread.CurrentThread.ManagedThreadId;
        int detectedSwitches = 0;
        for (int i = 0; i < 1000; i++)
        {
            int currentThread = Thread.CurrentThread.ManagedThreadId;
            if (lastKnownThread != currentThread)
            {
                detectedSwitches++;
                Console.WriteLine(
                    $"{detectedSwitches}: Last known thread ({lastKnownThread}) is not the same as the current thread ({currentThread}).");
                lastKnownThread = currentThread;
            }
            yield return rnd.Next(100,250);
        }
    }
}

在我的测试运行中,线程在 1000 次迭代中在 157 和 174 次之间切换。 Sleep 模拟我的操作所花费的时间。

总结

如果.NET 中实现的迭代器模式本质上不是线程安全的,为什么Parallel.ForEach 会这样做?和;什么是能够以安全的方式(在一个线程上)获取迭代器当前公开的数据,但在多个线程上处理它的好解决方案? (例如,有没有办法强制迭代器回到一个线程上?或者迭代器也必须是线程安全的,以及每次迭代调用的操作?或者完全是其他解决方案?)

版本历史

  • 更新了摘要,希望能避免或减少我原来问题的 XY 问题。

【问题讨论】:

    标签: c# .net multithreading task-parallel-library parallel.foreach


    【解决方案1】:

    有没有办法强制迭代器回到一个线程上?

    不,您必须明确处理多个线程调用迭代器的情况。在幕后,如果多个线程正在调用IEnumerator.MoveNext(),迭代器将继续前进,就像它被告知的那样。这里没有发生隐式同步。

    @JonSkeet blogged a while back 关于使用锁定进行迭代。虽然我必须说这对我来说看起来像是 XY 问题,但您是否应该从多个线程并行调用 NHibernate?它是上下文线程安全的吗?这些是您在使用线程安全迭代器之前应该考虑的问题。

    【讨论】:

    • 据我所知,由于在 .NET 中通过 IEnumerator 接口实现的迭代器模式本质上不是线程安全的(您引用的 Jon Skeet 博客中的示例显示了竞争条件:会发生)然后我希望 NHibernate 仅从一个线程调用。然而,事实证明,如果我将迭代器与Parallel.ForEach 一起使用,情况并非如此。所以,也许更好的问题是:为什么会发生这种情况?和;什么是安全地(从一个线程)获取数据但在多个线程中处理它的好解决方案?
    • @Colin 那么我希望 NHibernate 只能从一个线程调用 在这种情况下正好相反。很简单,迭代器是单个实例,由多个线程迭代。没有一个线程以“生产者-消费者”的方式负责迭代。它更像是狂野的西部。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    • 1970-01-01
    相关资源
    最近更新 更多