访问冲突异常之谜

Posted

技术标签:

【中文标题】访问冲突异常之谜【英文标题】:Access Violation Exception mystery 【发布时间】:2013-01-05 22:35:36 【问题描述】:

我使用 EMGU+OpenCV 已经有一段时间了,遇到了这个AccessViolationException 的谜团。

首先,代码:

class AVE_Simulation
    
        public static int Width = 7500;
        public static int Height = 7500;
        public static Emgu.CV.Image<Rgb, float>[] Images;

        static void Main(string[] args)
        
            int N = 50;
            int Threads = 5;

            Images = new Emgu.CV.Image<Rgb, float>[N];
            Console.WriteLine("Start");

            ParallelOptions po = new ParallelOptions();
            po.MaxDegreeOfParallelism = Threads;
            System.Threading.Tasks.Parallel.For(0, N, po, new Action<int>((i) =>
            
                Images[i] = GetRandomImage();
                Console.WriteLine("Prossing image: " + i);
                Images[i].SmoothBilatral(15, 50, 50);
                GC.Collect();
            ));
            Console.WriteLine("End");
        

        public static Emgu.CV.Image<Rgb, float> GetRandomImage()
        
            Emgu.CV.Image<Rgb, float> im = new Emgu.CV.Image<Rgb, float>(Width, Height);

            float[, ,] d = im.Data;
            Random r = new Random((int)DateTime.Now.Ticks);

            for (int y = 0; y < Height; y++)
            
                for (int x = 0; x < Width; x++)
                
                    d[y, x, 0] = (float)r.Next(255);
                    d[y, x, 1] = (float)r.Next(255);
                    d[y, x, 2] = (float)r.Next(255);
                
            
            return im;
        

    

代码很简单。分配一组图像。生成一个随机图像并用随机数填充它。对图像执行双边过滤。就是这样。

如果我在单个线程中执行此程序,(Threads=1) 一切似乎都可以正常工作,没有问题。 但是,如果我将并发线程数增加到 5,我会很快收到 AccessViolationException。

我检查了 OpenCV 代码并验证了 OpenCV 端没有分配,还检查了 EMGU 代码以搜索未固定的对象或其他问题,一切似乎都是正确的。

一些注意事项:

    如果您删除 GC.Collect(),您会较少收到 AccessViolationException,但它最终会发生。 只有在发布模式下执行时才会发生这种情况。在调试模式下,我没有遇到任何异常。 虽然每个 Image 是 675MB,但分配没有问题(我有 ALLOT 内存)并且会抛出“OutOfMemoryException”以防系统内存不足。 我使用了双边过滤器,但其他过滤器/功能也出现此异常。

任何帮助将不胜感激。一个多星期以来,我一直在努力解决这个问题。

i7(无超频),Win7 64bit,32GB RAM,VS 2010,Framework 4.0,OpenCV 2.4.3

堆栈:

Start
Prossing image: 20
Prossing image: 30
Prossing image: 40
Prossing image: 0
Prossing image: 10
Prossing image: 21

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.Tasks.ThreadPoolTaskScheduler.TryExecuteTaskInline(Task task, Boolean taskWasPreviouslyQueued)
   at System.Threading.Tasks.TaskScheduler.TryRunInline(Task task, Boolean taskWasPreviouslyQueued)
   at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler, Boolean waitForCompletion)
   at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 loc
alInit, Action`1 localFinally)
   at System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body)
   at TestMemoryViolationCrash.AVE_Simulation.Main(String[] args) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 35

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
Press any key to continue . . .

【问题讨论】:

异常发生时发布栈信息, 异常很明显。您正在尝试访问您的程序无法访问的内存(因为它没有填充它)。 我看不出有什么这么清楚。为什么在发布模式下会崩溃而不是在调试模式下?如果在单个线程中执行并在多个线程中崩溃,为什么它可以正常工作。请点亮我。 @Gilad 当尝试读取已处理的图像或多个线程共享相同的图像指针时,我通常会遇到此错误。不过,您的代码看起来很直接。 一段名为 XXXX 的代码中的 AccessViolationException 是(99% 的)XXXX 中的错误。您应该联系 Emgu.CV 的支持人员。这可能是因为你做了一些它不喜欢的事情,也许你可以找到解决方法,但这仍然是 Emgu.CV 中的一个错误。 【参考方案1】:

您的示例没有保留对 Image.SmoothBilatral 的 结果图像 的引用。输入图像植根于静态数组,所以很好。

Emgu.CV 图像的数据数组被固定到实际图像中的 GCHandle,这与图像包含数组并且在 GCHandle 的指针被非托管代码使用时不会阻止收集的事实没有什么不同(在图像没有托管根)。

因为 Image.SmoothBilatral 方法除了传递它的指针并返回它之外,不对其临时结果图像做任何事情,我认为它被优化到可以在平滑处理时收集结果图像的程度。

因为这个类没有终结器,opencv 不会被调用来释放它的非托管图像头(它有一个指向托管图像数据的指针),所以 opencv 仍然认为它有一个可用的图像结构。

您可以通过引用 SmoothBilatral 的结果并对其进行处理(例如处理它)来修复它。

此扩展方法也可以工作(即允许在不使用结果的情况下成功调用它进行基准测试):

public static class BilateralExtensionFix

    public static Emgu.CV.Image<testchannels, testtype> SmoothBilateral(this Emgu.CV.Image<testchannels, testtype> image, int p1, int p2 , int p3)
    
        var result = image.CopyBlank();
        var handle = GCHandle.Alloc(result);
        Emgu.CV.CvInvoke.cvSmooth(image.Ptr, result.Ptr, Emgu.CV.CvEnum.SMOOTH_TYPE.CV_BILATERAL, p1, p1, p2, p3);
        handle.Free();
        return result;
    

我认为 EmguCV 应该做的只是在进行互操作调用时固定指针以传递给 opencv。

p.s OpenCv 双边过滤器在所有通道上以零变化 (min() = max()) 传递的任何类型的浮动图像上崩溃(产生与您的问题非常相似的错误)。我认为因为它是如何构建它的 binned exp() 查找表的。

这可以通过以下方式复制:

    // create new blank image
    var zeroesF1 = new Emgu.CV.Image<Rgb, float>(75, 75);
    // uncomment next line for failure
    zeroesF1.Data[0, 0, 0] += 1.2037063600E-035f;
    zeroesF1.SmoothBilatral(15, 50, 50);

这让我很困惑,因为我实际上有时会因为我的测试代码中的错误而收到此错误...

【讨论】:

谢谢吉拉德。一个有趣的问题……教会了我很多我以为我已经知道的东西。 @PeterWishart 你能看看这个***.com/questions/50388992/…【参考方案2】:

您使用的是什么版本的 Emgu CV?我找不到它的 2.4.3 版本。

很确定您的代码不是问题

似乎 Emgu.CV.Image 构造函数可能存在并发问题(在托管包装器或非托管代码中)。在 Emgu CV 主干中处理托管数据数组的方式似乎很可靠,在图像构造函数期间分配了一些非托管数据,我想这可能出错了。

如果你尝试会发生什么:

Images[i] = GetRandomImage(); 移出并行 For()。 在GetRandomImage() 中的Image 构造函数周围打一个lock()

我注意到有人有类似问题的已关闭错误报告(对图像构造函数的调用并行发生,但图像本身未在线程之间共享)here。

[编辑]

是的,这很奇怪。我可以使用股票 2.4.2 版本和 OpenCV 二进制文件进行复制。

如果并行中的线程数超过对我来说 >2 的内核数,它似乎只会崩溃。知道您的测试系统上有多少内核会很有趣。

另外,我只有在代码未附加到调试器并且启用优化代码时才会崩溃 - 你有没有在附加调试器的发布模式下观察到它?

由于 SmoothBilateral 函数受 CPU 限制,使用 MaxDegreeOfParallelism 多于内核数量并不会真正增加任何好处,因此假设我发现的有关数量的完美解决方法,如果线程与内核也适用于您的装备(sods法律预测:不是)。

所以我的猜测是 Emgu 中存在并发/易失性问题,仅在运行 JIT 优化以及 GC 移动托管数据时才会出现。 但是,正如您所说,Emgu 代码中没有明显的 unpinned-pointer-to-managed-object 问题。

虽然我仍然无法正确解释,但这是我目前发现的:

删除 GC.Collect + 控制台日志后,对 GetRandomImage() 的调用序列化,并且代码在 MSVC 之外运行,我无法重现该问题(尽管这可能只是降低了频率):

            public static int Width = 750;
            public static int Height = 750;
...
                int N = 500;
                int Threads = 11;
                Images = new Emgu.CV.Image<Rgb, float>[N];
                Console.WriteLine("Start");
                ParallelOptions po = new ParallelOptions();
                po.MaxDegreeOfParallelism = Threads;
                for (int i = 0; i < N; i++)
                
                    Images[i] = GetRandomImage();
                
                System.Threading.Tasks.Parallel.For(0, N, po, new Action<int>((i) =>
                
                    //Console.WriteLine("CallingSmooth");
                    Images[i].SmoothBilatral(15, 50, 50);
                    //Console.WriteLine("SmoothCompleted");
                ));
                Console.WriteLine("End");

我添加了一个计时器来在并行之外触发 GC.Collect,但仍然比正常触发的频率更高:

        var t = new System.Threading.Timer((dummy) =>  
            GC.Collect(); 
        , null, 100,100);

通过此更改,我仍然无法重现该问题,尽管由于线程池繁忙,GC 收集的调用不如您的演示中那么一致,而且主内存中也没有(或很少)发生托管分配循环让它收集。 取消注释 SmoothBilatral 调用周围的控制台日志,然后相当迅速地重现错误(我猜是通过给 GC 一些东西来收集)。

[另一个编辑]

OpenCV 2.4.2 reference manual 声明 cvSmooth 已弃用,并且“中值和双边过滤器适用于 1 或 3 通道 8 位图像,并且无法就地处理图像。”.. . 不是很鼓舞人心!

我发现在字节图像或浮点图像上使用中值过滤器并在字节图像上使用双边滤波器效果很好,我不明白为什么任何 CLR/GC 问题也不会影响这些情况。

因此,尽管对 C# 测试程序产生了奇怪的影响,但我仍然认为这是一个 Emgu/OpenCV 错误。

如果您还没有,您应该使用 compiled yourself 的 opencv 二进制文件进行测试,如果仍然失败,请将您的测试转换为 C++。

注意OpenCV 有它的own parallelism implementation,这可能会更快。

【讨论】:

我使用了 EMGU 2.4.2 并对其进行了调整以使用 OpenCV 2.4.3 二进制文件。没有重大更改,只有几个额外的方法(和错误修复)。我多次跟踪 EMGU 代码,似乎它编写正确(所有相关对象都已固定)。这就是为什么这个“错误”如此奇怪。 我尝试了你的建议。我将 GetRandomImage 从并行循环中取出,但仍然是 AccessViolationException。添加锁没有意义,因为将 ctor 移出并行循环与同步执行它是一样的。 我还检查了“已关闭的错误”。看来他的解决方案是将流程代码移到枚举器函数中。我非常肯定的是,当我们写到他找到了解决方案时,他没有注意到他的代码不再并行运行(我猜这是寻找解决方案的肾上腺素)...... 感谢您花时间研究此问题。我的 CPU 是带有 HT 的 4 核,因此它可以一次运行 8 个图像。问题很明显,即使某些对象被固定,它们也被 GC 移动(执行 GC.Collect 时)。我有一种强烈的感觉,这是 MS 框架中的一个错误。我还检查了 Framework 4.5 (VS2012) 并得到了同样的错误。 另一种可能性是来自非托管 OpenCV 代码的某种堆损坏,只有在 GC 工作时才会检测到(实际上可能不会,认为这会返回不同的错误)。

以上是关于访问冲突异常之谜的主要内容,如果未能解决你的问题,请参考以下文章

未处理的异常访问冲突错误

抛出异常:写访问冲突 C++

使用 DLL 函数时的访问冲突异常

forrtl:严重(157):程序异常 - 访问冲突

**** 访问冲突读取位置处未处理的异常 *******

QWidget“访问冲突”异常