防止编译器/ cpu指令重新排序c#

时间:2013-07-07 21:03:02

标签: c#

我有一个包含两个Int32的Int64:

[StructLayout(LayoutKind.Explicit)]
public struct PackedInt64
{
    [FieldOffset(0)]
    public Int64 All;
    [FieldOffset(0)]
    public Int32 First;
    [FieldOffset(4)]
    public Int32 Second;
}

现在我想要构造函数(对于所有第一和第二)。但是,struct需要在退出构造函数之前分配所有字段。 考虑 all 构造函数。

public PackedInt64(Int64 all)
{
    this.First = 0;
    this.Second = 0;
    Thread.MemoryBarrier();
    this.All = all;
}

我希望绝对确保在构造函数中最后分配this.All,以便在cpu中进行某些编译器优化或指令重新排序时,不会覆盖该字段的一半或更多字段。

Thread.MemoryBarrier()是否足够?这是最好的选择吗?

4 个答案:

答案 0 :(得分:13)

,这是防止重新排序的正确和最佳方法。

通过在示例代码中执行Thread.MemoryBarrier(),永远不会允许处理器重新排序指令,以便在访问后访问/修改FirstSecond /修改为All。由于它们都占用相同的地址空间,因此您不必担心以后的更改会被您之前的更改覆盖。

请注意Thread.MemoryBarrier()仅适用于当前正在执行的线程 - 它不是一种锁。但是,鉴于此代码在构造函数中运行,并且没有其他线程可以访问此数据,这应该是完全正常的。但是,如果确实需要跨线程保证操作,则需要使用锁定机制来保证独占访问。

请注意,您可能实际上并不需要在基于x86的计算机上使用此指令,但如果您有一天在另一个平台上运行(例如IA64),我仍会推荐该代码。请参阅下面的图表,了解哪些平台将在保存后重新排序内存,而不仅仅是后加载。

enter image description here

答案 1 :(得分:3)

MemoryBarrier会阻止重新排序,但此代码仍然存在。

LayoutKind.ExplicitFieldOffsetAttribute被记录为影响对象传递给非托管代码时的内存布局。它可用于与C union互操作,但不能用于模拟C union

即使它目前按照您期望的方式运行,但在您测试的平台上,也无法保证它会继续这样做。唯一的保证是在与非托管代码互操作的上下文中(即p / invoke,COM interop或C ++ / CLI它只是工作)。

如果要以便携式面向未来的方式读取字节子集,则必须使用按位运算或字节数组BitConverter。即使语法不太好。

答案 2 :(得分:2)

查看以下链接的评论部分:http://msdn.microsoft.com/en-us/library/system.threading.thread.memorybarrier.aspx

它说只有在内存排序较弱的多处理器系统上才需要MemoryBarrier()。因此,这是一个足够的选择,但这是否是最佳选择取决于您使用的系统。

答案 3 :(得分:0)

首先,我知道这个答案并没有真正解决重新排序问题,而是否定了它。通过使用不安全的代码,您可以避免完全写入FirstSecond

public unsafe PackedInt64(long all) {
    fixed (PackedInt64* ptr = &this)
        *(long*) ptr = all;
}

这并不是最优雅的解决方案,可能无法通过大多数有关托管代码的公司政策,但它应该有效。