.NET C#不安全/固定不销直通数组元素? [英] .NET C# unsafe/fixed doesn't pin passthrough array element?

查看:96
本文介绍了.NET C#不安全/固定不销直通数组元素?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些并发code其中有一个间歇性故障,我已经减下来的问题,两个案件似乎是相同的,但如果其中一个出现故障,另一个没有。

我现在已经花了太多时间试图创建一个失败的最小的,完整的例子,但没有成功,所以我只是张贴在任何情况下,失败线可以看到一个明显的问题。

 结构MyValueType {只读公众诠释I1,I2; };密封类节点
{
    公共MyValueType X;
    公众诠释Ÿ;
    公共节点Z;
};挥发性节点[] = m_rg新节点[300]; //注意:挥发性对象o =新的对象();不安全无效美孚()
{
    ///为挥发性数组引用一个本地副本
    节点[]温度;    ///获取锁,检查我们的本地副本仍然有效
    而(真)
    {
        TEMP = m_rg;
        / * ...做一些东西... * /
        Monitor.Enter(O);
        如果(临时== m_rg)
            打破;
        Monitor.Exit(O);
    }    ///下锁,写值的两个32位字段
    ///类型有一个64位写。任何人都无法改变
    ///要么阵列m_rg(本身),和值
    ///在指数33,而此锁被保留。别人可以,
    ///但是,仍然在其他改变Node对象(S)
    ///索引位置。#如果OK //这工作:    节点CUR =温度[33];
    固定(MyValueType * PE =安培; cur.x)
        *(*长)PE = *(*长)与评估;#else伪//这导致可靠的随机损坏:    固定(MyValueType * PE =&放大器;临时[33] .X)
        *(*长)PE = *(*长)与评估;#万一    Monitor.Exit(O);
}

我已经研究了IL code,它看起来像发生的事情是,在排列第33位节点对象(在极少数情况下),尽管大家都抱着一个指针的值类型中移动它

这是因为如果在CLR没有注意到,我们传递的的堆(移动)对象 - 数组元素 - 为了访问值类型。 确定版本从未在扩展测试失败,8路机,但备用路径,很快失败每次。



  • 难道这绝不应该工作,和'OK'版本太流线型的压力下失败?

  • 请我需要自己的脚使用的GCHandle对象(我在IL注意到固定语句不能单独这样做)?

  • 如果这里需要手工钉扎,为什么编译器允许通过一个堆对象,以这种方式访问​​(无钉)?

注:这个问题是不是在讨论reinter $ P $的优雅的一个讨厌的方式pting的blittable值类型,所以请,没有code这方面的批评,除非它是直接相关手头的问题..谢谢


多亏了汉斯的答复,我更好地理解了为什么抖动把东西堆在什么否则看似空洞的ASM操作。见[RSP + 50H]例如,连带如何固定区域后置空出来。其余悬而未决的问题是在栈上[CUR + 18H(行207-20C)是否是某种足以保护的方式,是的的访问的值类型足够[温度+ 33 * IntPtr.Size + 18H(行24A)。

结论综上所述,小例子

相比有以下两个code片段,我现在相信,#1是不正常的,而#2是可以接受的。

下面的失败(在x64 JIT至少)(1);如果你尝试的,通过一个数组引用来解决它的原位GC仍然可以移动的 MyClass的实例。有堆栈的特定对象实例(需要被固定数组元素)的参考上没地方发表,对GC的注意。

 结构MyValueType {公众诠释富; };
MyClass类{公共MyValueType MVT; };
MyClass的[] = RGO新MyClass的[2000];固定(MyValueType * PVT =安培; RGO [1234] .mvt)
    *(INT *)PVT = 1234;

(2)。但是您可以使用访问(移动)对象内部结构的固定的(无钉)如果你提供在堆栈上明确提到其可以直接发布到GC:

 结构MyValueType {公众诠释富; }; //相同
MyClass类{公共MyValueType MVT; }; //相同
MyClass的[] = RGO新MyClass的[2000]; //相同MyClass的MC =安培; RGO [1234]; //除了
固定(MyValueType * PVT =安培; mc.mvt)//改变
    *(INT *)PVT = 1234; //相同

这是我会离开它,除非有人能提供更正或更多信息...


解决方案

  

通过固定指针修改托管类型的对象可能导致不确定的行为
(C#语言规范,章18.6。)


好了,你这样做。尽管在规范的空话和MSDN库,在固定的关键字其实并没有使对象不可摇动,它不会固定。你可能从看IL发现了。它通过产生一个指针+偏移量,让垃圾回收器调整指针使用智能把戏。我没有一个很好的解释为什么这个失败的一个案例而不是其他。我没有看到在生成的机器code有根本的区别。但后来我大概没有重现您的确切机器code要么,片段也不是很大。

尽可能接近我可以告诉它应该因为结构成员访问这两种情况下会失败。这会导致指针偏移+崩溃到单个指针与执法机关的指令,$ P $无法识别的参考pventing垃圾收集器。结构一直在抖动麻烦。螺纹时机可以解释的差异,也许吧。

您可以张贴到connect.microsoft.com的第二个观点。但它是将是难以导航围绕规范违反。如果我的理论是正确的,然后读取可能会失败过,更难虽然证明。

通过实际钉住的数组的GCHandle修复它。

I have some concurrent code which has an intermittent failure and I've reduced the problem down to two cases which seem identical, but where one fails and the other doesn't.

I've now spent way too much time trying to create a minimal, complete example that fails, but without success, so I'm just posting the lines that fail in case anyone can see an obvious problem.

struct MyValueType { readonly public int i1, i2; };

sealed class Node
{
    public MyValueType x;
    public int y;
    public Node z;
};

volatile Node[] m_rg = new Node[300];  // note: volatile

Object o = new Object();

unsafe void Foo()
{
    /// for a local copy of volatile array reference
    Node[] temp;

    /// obtain a lock and check that our local copy is still valid
    while (true)
    {
        temp = m_rg;
        /* ...do some stuff... */
        Monitor.Enter(o);
        if (temp == m_rg)
            break;
        Monitor.Exit(o);
    }

    /// under lock, write the two 32-bit fields of the value
    /// type with a single 64-bit write. nobody can change
    /// either the array m_rg (itself), nor the value
    /// at index 33 while this lock is held. others can,
    /// however, still change the Node object(s) at other
    /// index positions.

#if OK  // this works:

    Node cur = temp[33];
    fixed (MyValueType* pe = &cur.x)
        *(long*)pe = *(long*)&e;

#else // this reliably causes random corruption:

    fixed (MyValueType* pe = &temp[33].x)
        *(long*)pe = *(long*)&e;

#endif

    Monitor.Exit(o);
}

I have studied the IL code and it looks like what's happening is that the Node object at array position 33 is moving (in very rare cases) despite the fact that we are holding a pointer to a value type within it.

It's as if the CLR doesn't notice that we are passing through a heap (movable) object--the array element--in order to access the value type. The 'OK' version has never failed under extended testing on an 8-way machine, but the alternate path fails quickly every time.

  • Is this never supposed to work, and 'OK' version is too streamlined to fail under stress?
  • Do I need to pin the object myself using GCHandle (I notice in the IL that the fixed statement alone is not doing so)?
  • If manual pinning is required here, why is the compiler allowing access through a heap object (without pinning) in this way?

note: This question is not discussing the elegance of reinterpreting the blittable value type in a nasty way, so please, no criticism of this aspect of the code unless it is directly relevant to the problem at hand.. thanks

[edit: jitted asm] Thanks to Hans' reply, I understand better why the jitter is placing things on the stack in what otherwise seem like vacuous asm operations. See [rsp + 50h] for example, and how it gets nulled out after the 'fixed' region. The remaining unresolved question is whether [cur+18h] (lines 207-20C) on the stack is somehow sufficient to protect the access to the value type in a way that is not adequate for [temp+33*IntPtr.Size+18h] (line 24A).

[edit]

summary of conclusions, minimal example

Comparing the two code fragments below, I now believe that #1 is not ok, whereas #2 is acceptable.

(1.) The following fails (on x64 jit at least); GC can still move the MyClass instance if you try to fix it in-situ, via an array reference. There's no place on the stack for the reference of the particular object instance (the array element that needs to be fixed) to be published, for the GC to notice.

struct MyValueType { public int foo; };
class MyClass { public MyValueType mvt; };
MyClass[] rgo = new MyClass[2000];

fixed (MyValueType* pvt = &rgo[1234].mvt)
    *(int*)pvt = 1234;

(2.) But you can access a structure inside a (movable) object using fixed (without pinning) if you provide an explicit reference on the stack which can be advertised to the GC:

struct MyValueType { public int foo; };      // same
class MyClass { public MyValueType mvt; };   // same
MyClass[] rgo = new MyClass[2000];           // same

MyClass mc = &rgo[1234];                     // addition
fixed (MyValueType* pvt = &mc.mvt)           // change
    *(int*)pvt = 1234;                       // same

This is where I'll leave it unless someone can provide corrections or more information...

解决方案

Modifying objects of managed type through fixed pointers can results in undefined behavior
(C# Language specification, chapter 18.6.)

Well, you are doing just that. In spite of the verbiage in the spec and the MSDN library, the fixed keyword does not in fact make the object unmoveable, it doesn't get pinned. You probably found out from looking at the IL. It uses a clever trick by generating a pointer + offset and letting the garbage collector adjust the pointer. I don't have a great explanation why this fails in one case but not the other. I don't see a fundamental difference in the generated machine code. But then I probably didn't reproduce your exact machine code either, the snippet isn't great.

As near as I can tell it should fail in both cases because of the structure member access. That causes the pointer + offset to collapse to a single pointer with a LEA instruction, preventing the garbage collector from recognizing the reference. Structures have always been trouble for the jitter. Thread timing could explain the difference, perhaps.

You could post to connect.microsoft.com for a second opinion. It is however going to be difficult to navigate around the spec violation. If my theory is correct then a read could fail too, much harder to prove though.

Fix it by actually pinning the array with GCHandle.

这篇关于.NET C#不安全/固定不销直通数组元素?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆