【问题标题】:Plinq statement gets deadlocked inside static constructorPlinq 语句在静态构造函数中死锁
【发布时间】:2011-04-24 12:27:02
【问题描述】:

我遇到了这种情况,静态构造函数中的以下 plinq 语句被死锁:

static void Main(string[] args)
{
    new Blah();
}

class Blah
{
     static Blah()
     {
         Enumerable.Range(1, 10000)
            .AsParallel()
            .Select(n => n * 3)
            .ToList();
     }
}

仅当构造函数是静态的时才会发生。 谁能给我解释一下。

这是 TPL 错误吗?编译器?我?

【问题讨论】:

  • 您是否有 Windows 编译的二进制文件,以便我可以比较生成的 MSIL?我不相信这是一个仅限图书馆的问题,我很好奇:)
  • 你可以从这里下载代码和二进制文件:cid-c152e66cc73fcce1.skydrive.live.com/…

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


【解决方案1】:

从静态构造函数调用线程代码通常很危险。为了保证静态构造函数只执行一次,CLR 在锁下执行静态构造函数。如果运行静态构造函数的线程在辅助线程上等待,那么辅助线程也有可能由于某种原因需要 CLR 内部锁,从而导致程序死锁。

这是一个演示问题的更简单的代码示例:

using System.Threading;
class Blah
{
    static void Main() { /* Won’t run because the static constructor deadlocks. */ }

    static Blah()
    {
        Thread thread = new Thread(ThreadBody);
        thread.Start();
        thread.Join();
    }

    static void ThreadBody() { }
}

ECMA CLI spec 的第 10.5.3.3 节“竞争和死锁”保证以下内容:

类型初始化本身不会造成死锁,除非某些代码 从类型初始值设定项(直接或间接)显式调用 调用阻塞操作。

因此,类型初始化器(即静态构造器)不会死锁,前提是静态构造器中没有任何操作阻塞线程。如果静态构造函数确实阻塞了,就会有死锁的风险。

【讨论】:

  • 这是一个非常好的观点。感谢你的回答。这只是不直观的东西。至少如果它以某种方式抛出异常......
【解决方案2】:

虽然已经解释了为什么您不想在静态构造函数中进行线程化工作的原因,但我想我要补充的是,这样做的“正确”方法是使用静态 @987654321 @。这也更有效,因为生成这些资源的工作将被推迟到实际需要这些资源时。

class Blah
{
    // Supply factory method to generate the numbers, but actual generation will be deferred
    private static Lazy<List<int>> MyMagicNumbers = new Lazy<List<int>>(Blah.GenerateMagicNumbers);

    public void DoSomethingWithMagicNumbers()
    {
        // Call to Lazy<T>.Value will synchronize any calling threads until value is initially generated from the factory
        List<int> magicNumbers = Blah.MyMagicNumbers.Value;

        // ... do something here ...
    }

    private List<int> GenerateMagicNumbers()
    {
        return Enumerable.Range(1, 10000)
                          .AsParallel()
                          .Select(n => n * 3)
                          .ToList();
    }
}

【讨论】:

    【解决方案3】:

    不管它的价值如何,这个问题不会出现在 Mono 上:

    [mono] /tmp @ dmcs par.cs 
    [mono] /tmp @ mono ./par.exe 
    

    您是否有 Windows 编译的二进制文件以便我可以比较生成的 MSIL?我不相信这是一个仅限图书馆的问题,我很好奇:)


    比较 IL 有点混乱,所以我决定在两个平台上都尝试这两个二进制文件。 呵呵,我恢复了我的旧 Windows 虚拟机只是为了测试这个:)

    在 Mono 上运行 VS 编译的二进制文件没有问题。您可以使用 2.10.1 (http://www.go-mono.com/mono-downloads/download.html) 在 Windows 上试用它,只有 77.4Mb :)

    我在 linux 上使用了自定义构建的 mono 2.11,所以可能是功能支持还没有完成

         \ run on platform:      MS.Net 4.0      Mono 2.1x
    built on: -------------+----------------------------------------
        Visual Studio       |      deadlock       no deadlock
                            |
        MonoDevelop         |      deadlock       no deadlock
    

    我还注意到,在 Windows 上运行时,CTRL-C 能够打破锁定。 如果我能找到更多内容,我会发布。


    更新 2

    好吧,即使在 Windows 上,安装 Mono 也会绕着安装 VSExpress 运行。安装单声道在 4 分钟内完成,结果是:

    C:\Users\Seth>"c:\Program Files (x86)\Mono-2.10.1\bin\mono.exe" ConsoleApplication2.exe
    C:\Users\Seth>
    

    没有死锁 :) 现在剩下的就是等待安装 VSExpress(永远)并安装调试工具(未知),然后破解它(可能直到深夜)。 CU以后

    【讨论】:

    • 这里有一个 Windows 二进制文件:cid-c152e66cc73fcce1.skydrive.live.com/…
    • 我暂时放弃了。无法让 VSExpress 在全新的(从不运行 :))Win7_64 VM 上运行。安装服务包失败(?!)所以我今天没有关注 WinDbg
    • 感谢您的辛勤工作。这似乎是 Windows 实现的 TPL 中的一个错误。
    • 如果死锁是一种预期的行为,我想知道为什么它不会在单声道中发生。他们不是锁定静态构造函数吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    • 2011-03-01
    • 2022-01-21
    • 2011-04-19
    • 2011-05-06
    • 2020-05-05
    相关资源
    最近更新 更多