【问题标题】:Deadlock with lock around and inside Parallel.ForEachParallel.ForEach 周围和内部的死锁
【发布时间】:2023-03-03 12:26:01
【问题描述】:

你能解释一下为什么这段代码会死锁吗?

int[] testlist = new int[ ] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
lock ( testlock ) {
    Parallel.ForEach( testlist, new ParallelOptions( ) { MaxDegreeOfParallelism = 90 }, ( int i ) => {
        Console.WriteLine( "hi there before " + i + " " + Monitor.IsEntered( testlock ) );
        lock ( testlock ) {
            Console.WriteLine( "hi there inner " + i + " " + Monitor.IsEntered( testlock ) );
        }
        Console.WriteLine( "hi there after " + i + " " + Monitor.IsEntered( testlock ) );
    } );
}

当然没有环绕锁这个代码不会死锁。

编辑:

感谢您的解释。典型的输出是:

hi there before 3 True
hi there inner 3 True
hi there after 3 True
hi there before 4 True
hi there inner 4 True
hi there after 4 True
hi there before 5 True
hi there inner 5 True
hi there after 5 True
hi there before 6 True
hi there inner 6 True
hi there after 6 True
hi there before 7 True
hi there inner 7 True
hi there after 7 True
hi there before 8 True
hi there inner 8 True
hi there after 8 True
hi there before 9 True
hi there inner 9 True
hi there after 9 True
hi there before 10 True
hi there inner 10 True
hi there after 10 True

事实上,典型的执行涉及我机器上的两个线程:一个被阻塞等待锁定(“1”一个),另一个正在运行其他迭代(从 3 到 10,注意输出的线性)。 “1”线程永远等待。现在清楚了,谢谢!

【问题讨论】:

  • 假设主线程是thread1。 Thread1 锁定testLock。由Parallel.ForEach 生成的所有其他线程(与线程1 不同的线程!)也希望锁定testLock,但它仍被线程1 锁定。 Thread1 等待所有Parallel-threads 完成=死锁。
  • 如果Parallel 决定在当前主线程(调用Parallel.ForEach 的那个线程)上运行所有迭代,那么这段代码永远可以工作的唯一方法。据我所知,如果你只有一个核心,它可能会选择这样做......在所有其他情况下:是的,它会死锁......因为你已经写了一个死锁。

标签: c# locking task-parallel-library deadlock parallel.foreach


【解决方案1】:

这是行不通的,因为你肯定在这里造成了死锁:

外部lock(testlock) 锁定testlock,然后Parallel.ForEach 开始在多个线程中处理您的testlist。现在这些线程到达内部lock(testlock),但由于它仍然被其线程中的外部线程锁定,因此无法再次锁定它=>外部lock直到处理完成才释放,但处理可以直到外部 lock 释放才完成 -> 死锁...

您可以使用MaxDegreeOfParallelism = 1 轻松验证这一点:然后一切都在主线程中运行,并且级联锁不会死锁,因为它可以再次锁定,因为它是同一个线程。

你想做什么 - 如果你解释一下,我们可以帮忙吗?

注意:
如果您的系统没有 90! CPU,则使用 MaxDegreeOfParallelism = 90 对性能没有帮助 - 建议将该数字保持在小于或等于您的 CPU/核心数(您可以通过Environment.ProcessorCount)。

【讨论】:

  • 我将其设置为 90 只是为了尝试使用不同的值(但是,我会很高兴拥有 90 cpu 的东西 :))
【解决方案2】:

在启动 Parallel.ForEach(第一次锁定)之前,您已锁定 testLock。在 Parallel.ForEach 内部,您锁定了同一个对象。 ForEach 在某个点使用不同的线程,而不是获得第一个锁的线程。该线程将等到锁可用,但它永远不会可用,因为在 ForEach 完成之前不会释放第一个锁。

由于ForEach 在所有线程都完成之前不会退出,因此您最终会陷入死锁。

【讨论】:

  • 我觉得值得一提的是,主线程等待Parallel产生的所有线程完成。
  • @pwas 这实际上并不完全准确。 “主线程”实际上可能是Parallel.ForEach 使用的线程之一,所以说那个线程只是坐在那里等待是不正确的,但我想我理解你的意图。
  • @pwas 好吧,你说线程制作,所以我想我现在明白你在说什么了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-26
  • 2011-09-27
  • 1970-01-01
相关资源
最近更新 更多