问题描述
阅读这个问题,我想测试,如果我能证明了非原子的读取和对这种活动的原子性不能保证一个类型写道。
Reading this question, I wanted to test if I could demonstrate the non-atomicity of reads and writes on a type for which the atomicity of such operations is not guaranteed.
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;
}
}
要我吃惊的是,断言拒绝了一个完整的3分钟执行后,甚至失败。怎么办?
To my surprise, the assertion refused to fail even after a full three minutes of execution.What gives?
- 在测试不正确。
- 该测试的特定定时特性使得它不太可能/不可能的断言将会失败。
- 的概率是如此之低,我要运行测试更长的时间,使之有可能会触发。
- 在CLR提供有关原子比C#规格更强的保障。
- 在我的OS /硬件提供强大的保障比CLR。
- 在其他什么东西?
当然,我并不打算靠没有明确的规范担保的行为,但我想这个问题有更深的了解。
Of course, I don't intend to rely on any behaviour that is not explicitly guaranteed by the spec, but I would like a deeper understanding of the issue.
仅供参考,我跑这两个调试和发布(改变 Debug.Assert的
到如果(..)抛出
在两个独立的环境中)的配置文件:
FYI, I ran this on both Debug and Release (changing Debug.Assert
to if(..) throw
) profiles in two separate environments:
- 在Windows 7的64位+ .NET 3.5 SP1
- 在Windows XP的32位+ .NET 2.0
编辑:要排除约翰Kugelman的评论的可能性调试器是不是薛定谔安全是这个问题,我添加了一行 someList.Add(DCOPY);
到记preading
方法,并确认这份名单并没有看到从缓存单一陈旧的价值。
To exclude the possibility of John Kugelman's comment "the debugger is not Schrodinger-safe" being the problem, I added the line someList.Add(dCopy);
to the KeepReading
method and verified that this list was not seeing a single stale value from the cache.
编辑:根据丹·布莱恩特的建议:使用长
而不是双
打破它几乎立刻
Based on Dan Bryant's suggestion: Using long
instead of double
breaks it virtually instantly.
推荐答案
您可以尝试通过 CHESS运行它,看它是否可以强制将打破测试交织。
You might try running it through CHESS to see if it can force an interleaving that breaks the test.
如果你看一看在x86 diassembly(可见从调试器),你可能也看到,如果抖动产生的preserve原子指令。
If you take a look at the x86 diassembly (visible from the debugger), you might also see if the jitter is generating instructions that preserve atomicity.
编辑:我继续跑拆装(强制目标86)。相关的线路有:
I went ahead and ran the disassembly (forcing target x86). The relevant lines are:
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]
有使用单个FSTP四字ptr的执行在这两种情况下的写入操作。我的猜测是,Intel的CPU,保证这个操作的原子性,虽然我还没有发现任何文件,以支持这一点。任何基于x86大师谁可以证实这一点?
It uses a single fstp qword ptr to perform the write operation in both cases. My guess is that the Intel CPU guarantees atomicity of this operation, though I haven't found any documentation to support this. Any x86 gurus who can confirm this?
更新:
不过,如果你使用的Int64,它采用了32位寄存器的x86处理器,而不是特殊的FPU寄存器的预期。你可以看到这个如下:
This fails as expected if you use Int64, which uses the 32-bit registers on the x86 CPU rather than the special FPU registers. You can see this below:
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
更新:
UPDATE:
我很好奇,如果,如果我被迫在内存中的双场非8字节对齐,这将失败,所以我把这个一起code:
I was curious if this would fail if I forced non-8byte alignment of the double field in memory, so I put together this code:
[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;
}
}
有不会失败和产生的x86指令基本上与以前相同:
It does not fail and the generated x86 instructions are essentially the same as before:
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]
我尝试交换_d1和_d2的用法DCOPY /套,也尝试了2。所有一FieldOffset产生相同的基本指令(用不同的偏移量以上)和所有的几秒钟后,没有失败(可能数十亿的尝试) 。我小心翼翼的信心,给了这些结果,至少有英特尔的x86 CPU提供了无论线形双加载/存储操作的原子性。
I experimented with swapping _d1 and _d2 for usage with dCopy/set and also tried a FieldOffset of 2. All generated the same basic instructions (with different offsets above) and all did not fail after several seconds (likely billions of attempts). I'm cautiously confident, given these results, that at least the Intel x86 CPUs provide atomicity of double load/store operations, regardless of alignment.
这篇关于为什么不这样code证明非原子的读/写?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!