【问题标题】:PLINQ iteration of looping enumerator causes deadlock循环枚举器的 PLINQ 迭代导致死锁
【发布时间】:2017-04-13 20:53:30
【问题描述】:

我有一个简单的程序,它迭代一个作为反馈枚举器实现的无限枚举。我已经在 TPL 和 PLINQ 中实现了这一点。这两个示例都在可预测的迭代次数后锁定:PLINQ 8 次,TPL 3 次。如果代码在不使用 TPL/PLINQ 的情况下执行,它运行良好。我以非线程安全的方式和线程安全的方式实现了枚举器。如果并行度限制为一个(如示例中的情况),则可以使用前者。非线程安全枚举器非常简单,不依赖于任何“花哨”的 .NET 库类。如果我增加并行度,死锁之前执行的迭代次数就会增加,例如对于 PLINQ,迭代次数是 8 * 并行度。

以下是迭代器:
枚举器(非线程安全)

public class SimpleEnumerable<T>: IEnumerable<T>
{
    private T _value;
    private readonly AutoResetEvent _releaseValueEvent = new AutoResetEvent(false);

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator<T> GetEnumerator()
    {
        while(true)
        {
            _releaseValueEvent.WaitOne();
            yield return _value;
        }
    }

    public void OnNext(T value)
    {
        _value = value;
        _releaseValueEvent.Set();
    }
}

枚举器(线程安全)

public class SimpleEnumerable<T>: IEnumerable<T>
{
    private readonly BlockingCollection<T> _blockingCollection = new BlockingCollection<T>();

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator<T> GetEnumerator()
    {
        while(true)
        {
            yield return _blockingCollection.Take();
        }
    }

    public void OnNext(T value)
    {
        _blockingCollection.Add(value);
    }
}

PLINQ 示例:

public static void Main(string[] args)
{
    var enumerable = new SimpleEnumerable<int>();
    enumerable.OnNext(0);

    enumerable
        .Do(i => Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}"))
        .AsParallel()
        .WithDegreeOfParallelism(1)
        .ForEach
        (
            i =>
            {
                Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}");
                enumerable.OnNext(i+1);
            }
        );
}

TPL 示例:

public static void Main(string[] args)
{
    var enumerable = new SimpleEnumerable<int>();
    enumerable.OnNext(0);

    Parallel.ForEach
    (
        enumerable,
        new ParallelOptions { MaxDegreeOfParallelism = 1},
        i =>
        {
            Debug.WriteLine($"{i} {Thread.CurrentThread.ManagedThreadId}");
            enumerable.OnNext(i+1);
        }
    );
}

根据我对调用堆栈的分析,似乎在 PLINQ 和 TPL 中的分区程序相关方法中都发生了死锁,但我不确定如何解释。

通过反复试验,我发现将 PLINQ enumerable 包装在 Partitioner.Create(enumerable, EnumerablePartitionerOptions.NoBuffering) 中可以解决问题,但我不确定为什么会发生死锁。

我很想找出错误的根本原因。

请注意,这是一个人为的示例。我不是在寻找对代码的批评,而是为什么会发生死锁。具体来说,在 PLINQ 示例中,如果 .AsParallel() 和 .WithDegreeOfParallelism(1) 行被注释掉,代码就可以正常工作。

【问题讨论】:

  • PLINQ 和 Parallel 不会造成死锁,它们使用当前线程和其他 N 个线程并行处理数据
  • @PanagiotisKanavos 显然死锁在他的迭代器中。乍一看,我并不感到惊讶,它肯定看起来一点也不安全。
  • @Servy 我从最明显的问题开始。迭代器 ... 是一个非常非常奇怪的构造。一个简单的 10K 整数数组足以测试并行执行。 Interlocked.Increment 将是一个很好的计数方式。这个迭代器,只是阻塞
  • 你的代码并没有真正做任何事情。在不知道它应该做什么的情况下,人们无法真正说出如何正确地完成它。
  • 这段代码唯一显示的是阻塞在单个线程上的迭代器是坏的。 实际问题是什么?

标签: c# .net parallel-processing task-parallel-library plinq


【解决方案1】:

您实际上没有值的逻辑序列,因此首先尝试创建IEnumerable 根本没有任何意义。此外,您几乎肯定不应该尝试创建一个可以被多个线程使用的IEnumerator。那里很疯狂,仅仅是因为IEnumerator 暴露的界面并没有真正暴露你想要它做的事情。您可能会创建一个 IEnumerator,它只会被单个线程使用,该线程根据多个线程使用的底层数据源计算要返回的数据,因为这完全不同。

如果您只是想创建在不同线程中运行的生产者和消费者,请不要围绕BlockingCollection 创建自己的“包装器”,*只需使用BlockingCollection。让生产者添加到它,消费者从中读取。消费者可以使用GetConsumingEnumerable,如果它只是想在获取这些项目时迭代项目(想要做的常见操作)。

【讨论】:

  • 我给出的例子是从完全不同的代码中提炼出来的。这是一个人为的例子来演示这个问题。原始代码没有使用枚举器,或者阻塞集合。我很欣赏你的 cmets,但我想说你完全错过了练习的重点。我并不是要批评代码在做什么或代码是如何做的,而是要批评 PLINQ 和 TPL 中的代码为什么会死锁。
  • @TomasC 你说你真正关心的代码看起来一点也不像你展示的代码,这意味着你不可能得到任何关于你关心什么的信息。如果您在实际上没有枚举器并且它不共享示例的任何实现细节时询问有关如何从多个线程中使用迭代器的问题,那么我们实际上对您的情况一无所知,并且显然无法评论在上面。我们只能对您实际提供的代码发表评论。
  • 我很清楚如何使用枚举器。我没有询问如何从多个线程中使用枚举器。我知道该怎么做。我只是在 PLINQ 代码中有一些导致死锁的行为,我希望能找到它为什么会死锁的原因(正如我在之前的评论中明确指出的那样)。您似乎忽略了我要问的问题,即为什么会陷入僵局?这就是我想知道的。否则,您的 cmets 将受到高度赞赏且非常有见地。
  • @TomasC 代码的结构完全荒谬,毫无疑问,它甚至不是解决任何问题的正确工具。您在不应该使用并且显然没有使用的方法中遇到的死锁对任何人都没有意义。如果您使用适当的工具,您的代码就不会死锁。如果您的完全不同的解决方案(这显然不是一个具有代表性的示例)是死锁,那么它与此代码以及它可能存在的任何死锁无关(您自己承认)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-14
  • 2010-11-09
  • 2014-10-28
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
  • 2012-06-08
相关资源
最近更新 更多