Ani*_*Ani 11 .net c# double atomic thread-safety
阅读 这个问题,我想测试一下我是否可以证明对这种操作的原子性无法保证的类型的读写非原子性.
private static double _d;
[STAThread]
static void Main()
{
new Thread(KeepMutating).Start();
KeepReading();
}
private static void KeepReading()
{
while (true)
{
double dCopy = _d;
// In release: if (...) throw ...
Debug.Assert(dCopy == 0D || dCopy == double.MaxValue); // Never fails
}
}
private static void KeepMutating()
{
Random rand = new Random();
while (true)
{
_d = rand.Next(2) == 0 ? 0D : double.MaxValue;
}
}
Run Code Online (Sandbox Code Playgroud)
令我惊讶的是,即使在执行了整整三分钟之后,断言也拒绝失败.是什么赋予了?
当然,我不打算依赖规范没有明确保证的任何行为,但我想更深入地了解这个问题.
仅供参考,我在两个独立环境中的调试和发布(更改Debug.Assert为 if(..) throw)配置文件上运行此操作:
编辑:为了排除John Kugelman的评论"调试器不是Schrodinger安全"的可能性,我将该行添加someList.Add(dCopy);到该KeepReading方法并验证该列表没有从缓存中看到单个过时值.
编辑:根据丹·布莱恩特的建议:使用long而不是double几乎立即休息.
Dan*_*ant 12
您可以尝试通过CHESS运行它以查看它是否可以强制交错打破测试.
如果您查看x86 diassembly(从调试器中可见),您可能还会看到抖动是否正在生成保持原子性的指令.
编辑:我继续运行反汇编(强制目标x86).相关的路线是:
double dCopy = _d;
00000039 fld qword ptr ds:[00511650h]
0000003f fstp qword ptr [ebp-40h]
_d = rand.Next(2) == 0 ? 0D : double.MaxValue;
00000054 mov ecx,dword ptr [ebp-3Ch]
00000057 mov edx,2
0000005c mov eax,dword ptr [ecx]
0000005e mov eax,dword ptr [eax+28h]
00000061 call dword ptr [eax+1Ch]
00000064 mov dword ptr [ebp-48h],eax
00000067 cmp dword ptr [ebp-48h],0
0000006b je 00000079
0000006d nop
0000006e fld qword ptr ds:[002423D8h]
00000074 fstp qword ptr [ebp-50h]
00000077 jmp 0000007E
00000079 fldz
0000007b fstp qword ptr [ebp-50h]
0000007e fld qword ptr [ebp-50h]
00000081 fstp qword ptr ds:[00159E78h]
Run Code Online (Sandbox Code Playgroud)
它使用单个fstp qword ptr在两种情况下执行写操作.我的猜测是英特尔CPU保证了此操作的原子性,但我还没有找到任何文档来支持这一点.任何x86大师谁可以证实这一点?
更新:
如果使用Int64,它会按预期失败,后者使用x86 CPU上的32位寄存器而不是特殊的FPU寄存器.你可以在下面看到:
Int64 dCopy = _d;
00000042 mov eax,dword ptr ds:[001A9E78h]
00000047 mov edx,dword ptr ds:[001A9E7Ch]
0000004d mov dword ptr [ebp-40h],eax
00000050 mov dword ptr [ebp-3Ch],edx
Run Code Online (Sandbox Code Playgroud)
更新:
我很好奇如果我在内存中强制非双字段对齐8字节会失败,所以我把这段代码放在一起:
[StructLayout(LayoutKind.Explicit)]
private struct Test
{
[FieldOffset(0)]
public double _d1;
[FieldOffset(4)]
public double _d2;
}
private static Test _test;
[STAThread]
static void Main()
{
new Thread(KeepMutating).Start();
KeepReading();
}
private static void KeepReading()
{
while (true)
{
double dummy = _test._d1;
double dCopy = _test._d2;
// In release: if (...) throw ...
Debug.Assert(dCopy == 0D || dCopy == double.MaxValue); // Never fails
}
}
private static void KeepMutating()
{
Random rand = new Random();
while (true)
{
_test._d2 = rand.Next(2) == 0 ? 0D : double.MaxValue;
}
}
Run Code Online (Sandbox Code Playgroud)
它不会失败,生成的x86指令基本上与以前相同:
double dummy = _test._d1;
0000003e mov eax,dword ptr ds:[03A75B20h]
00000043 fld qword ptr [eax+4]
00000046 fstp qword ptr [ebp-40h]
double dCopy = _test._d2;
00000049 mov eax,dword ptr ds:[03A75B20h]
0000004e fld qword ptr [eax+8]
00000051 fstp qword ptr [ebp-48h]
Run Code Online (Sandbox Code Playgroud)
我尝试交换_d1和_d2用于dCopy/set,并尝试使用FieldOffset 2.所有生成相同的基本指令(上面有不同的偏移量)并且几秒钟后都没有失败(可能数十亿次尝试).鉴于这些结果,我谨慎地相信至少英特尔x86 CPU提供双重加载/存储操作的原子性,无论是否对齐.