【问题标题】:AccessViolationException with Parallel.For()AccessViolationException 与 Parallel.For()
【发布时间】:2014-10-18 10:36:35
【问题描述】:

下面的代码 sn-p 展示了一个我能够从生产代码中隔离出来的错误。该程序将崩溃并显示System.AccessViolationException

我的系统配置是 Windows 8.1 x64 上的 Visual Studio 2013。要重现该错误,请在 Visual Studio 中执行以下步骤:

  1. 创建一个空的控制台应用程序
  2. 用下面的代码替换 Program.cs 的全部内容
  3. Release模式下编译,任何CPU
  4. 开始不调试(CTRL + F5)

程序将立即崩溃,并在控制台窗口中显示错误堆栈跟踪。

我无法进一步减少代码,即修改代码的任何部分都会使错误消失。

有什么方法可以找出这个错误的根本原因吗?什么是好的解决方法?

using System;
using System.Threading.Tasks;

namespace ParallelBugTest
{
    class Program
    {
        private class Value
        {
            public double X;
        }

        private class Aggregator
        {
            private Value myval = new Value();

            public void Add()
            {
                // use Min() instead of Max() -> bug disappears
                myval.X = Math.Max(0, myval.X);
            }
        }

        public static void Main(string[] args)
        {
            Parallel.For(0, 10000, Process);
        }

        private static void Process(int k)
        {
            Value[] V = new Value[10000];
            Aggregator aggregator = new Aggregator();

            for (int i = 0; i < V.Length; i++)
            {
                V[i] = new Value();
                aggregator.Add();
            }
        }
    }
}

【问题讨论】:

  • 有趣:Parallel.For(0, 2000, Process); 有时会崩溃。

标签: c# crash task-parallel-library parallel-extensions


【解决方案1】:

Math.Max 不是线程安全的。我猜 Min 正在工作,因为它的计算速度更快。 锁定 Max-Call 有效:

    private static Object lockObj = new Object();

    private class Value
    {
        public double X;
    }

    private class Aggregator
    {
        private Value myval = new Value();

        public void Add()
        {
            // use Min() instead of Max() -> bug disappears
            lock (lockObj)
            { 
                myval.X = Math.Max(0, myval.X);
            }
        }
    }

    public static void Main(string[] args)
    {
        Parallel.For(0, 10000, Process);
    }

    private static void Process(int k)
    {
        Value[] V = new Value[10000];
        Aggregator aggregator = new Aggregator();

        for (int i = 0; i < V.Length; i++)
        {
            V[i] = new Value();
            aggregator.Add();
        }
    }

快速简单的方法就是使用普通的iif:

        public void Add()
        {
            // use Min() instead of Max() -> bug disappears
            myval.X = myval.X > 0 ? myval.X : 0;
        }

【讨论】:

  • 我看不出任何Math 成员不会是线程安全的? Math 类的文档明确指出:“这种类型的任何公共静态成员都是线程安全的。不保证任何实例成员都是线程安全的。”
  • 不是线程安全的我的意思是它是与多线程结合的错误。正如 svick 所说,你可能想报告它。如果您需要一个快速的解决方案,您可以使用锁或创建自己的 Max-Method,如我的帖子所示。
【解决方案2】:

代码中的所有内容都是线程安全的,因为线程之间没有共享状态(每次迭代都有自己的AggregatorValues 数组,而您没有使用任何static 字段)。

即使您的代码不是线程安全的,它也不会做任何不安全的事情(即直接使用内存),这应该是获得AccessViolationException 的唯一方法。

因此,我认为这是 CLR 中的一个错误,您应该report it(登录后点击“提交反馈”链接)。

【讨论】:

  • 我可以证明这是与多线程结合的错误。所以我不把有问题的方法称为线程安全。
  • @MatthiasMüller 在the implementation of Math.Max() 中到底什么不是线程安全的?仅仅因为在这种特定情况下,仅当您将Math.Max() 与多线程一起使用时才会出现该错误并不意味着Math.Max() 不是线程安全的。
猜你喜欢
  • 2014-01-08
  • 1970-01-01
  • 2012-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多