我有一些并发故障的并发代码,我把问题减少到两个似乎相同的情况,但是哪一个失败而另一个没有。
我现在花了太多时间试图创建一个失败的最小的完整示例,但没有成功,所以我只是发布失败的行,以防任何人看到明显的问题。
Object lock = new Object();
struct MyValueType { readonly public int i1, i2; };
class Node { public MyValueType x; public int y; public Node z; };
volatile Node[] m_rg = new Node[300];
unsafe void Foo()
{
Node[] temp;
while (true)
{
temp = m_rg;
/* ... */
Monitor.Enter(lock);
if (temp == m_rg)
break;
Monitor.Exit(lock);
}
#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(lock);
}
我已经研究了IL代码,看起来正在发生的事情是数组位置33处的Node对象正在移动(在非常罕见的情况下),尽管我们正在持有指向其中的值类型的指针。
就好像CLR没有注意到我们正在通过通过传递堆(可移动)对象 - 数组元素 - 来访问值类型。在8路机器上进行扩展测试时,“OK”版本从未失败,但每次都会快速失败。
注意:这个问题并没有讨论以讨厌的方式重新解释blittable值类型的优雅,所以请不要批评代码的这个方面,除非它与手头的问题直接相关..谢谢
[编辑:jitted asm] 感谢Hans的回复,我更清楚为什么抖动会把东西放在堆栈中,看起来像是空的asm操作。例如,参见[rsp + 50h],以及它如何在“固定”区域之后被取消。剩下的未解决的问题是堆栈上的[cur + 18h](第207-20C行)是否足以保护对不足够[temp + 33]的方式对值类型的访问* IntPtr.Size + 18h](第24A行)。
[编辑]
比较下面的两个代码片段,我现在认为#1不合适,而#2是可以接受的。
(1。)以下失败(至少在x64 jit上);如果您尝试通过数组引用尝试修复 in-situ ,GC仍然可以移动 MyClass 实例。堆栈上没有地方可供发布的特定对象实例(需要修复的数组元素)的引用,以供GC注意。
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。)但是你可以使用 fixed (没有固定)来访问(可移动)对象内的结构,如果你在堆栈上提供了一个明确的引用,它可以被广告给GC:
struct MyValueType { public int foo; };
class MyClass { public MyValueType mvt; };
MyClass[] rgo = new MyClass[2000];
MyClass mc = &rgo[1234]; // <-- only difference -- add this line
fixed (MyValueType* pvt = &mc.mvt) // <-- and adjust accordingly here
*(int*)pvt = 1234;
除非有人可以提供更正或更多信息,否则我将离开它...
答案 0 :(得分:3)
通过固定指针修改托管类型的对象可能导致未定义的行为
(C#语言规范,第18.6章。)
嗯,你就是这样做的。尽管规范和MSDN库中有措辞,但 fixed 关键字实际上并不会使对象无法移动,也无法固定。您可能从查看IL时发现了。它通过生成指针+偏移并让垃圾收集器调整指针来使用一个聪明的技巧。我没有一个很好的解释,为什么在一个案例中失败但在另一个案例中失败。我没有看到生成的机器代码的根本区别。但是,我可能也没有重现你的确切机器代码,该片段不是很好。
尽管我可以告诉它,但由于结构成员访问,它在两种情况下都会失败。这会导致指针+偏移量折叠为带有LEA指令的单个指针,从而阻止垃圾收集器识别引用。结构一直是抖动的麻烦。线程时间也许可以解释这种差异。
您可以发布到connect.microsoft.com获取第二个意见。然而,围绕规范违规进行导航将很困难。如果我的理论是正确的,那么读取也可能失败,但更难以证明。
通过使用GCHandle实际固定数组来修复它。
答案 1 :(得分:0)
令人费解,我猜这里,看起来编译器正在采用&amp; temp(固定指向tmp数组的指针),然后用[33]索引。所以你要固定临时数组,而不是节点。尝试...
fixed (MyValueType* pe = &(temp[33]).x)
*(long*)pe = *(long*)&e;