20

我有一个包含两个 Int32 的 Int64,如下所示:

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

现在我想要构造函数(对于allfirst 和 second)。但是,该结构要求在退出构造函数之前分配所有字段。考虑all构造函数。

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

我想绝对确定它this.All是在构造函数中最后分配的,以便在某些编译器优化或 cpu 中的指令重新排序的情况下不会覆盖一半或更多的字段。

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

4

4 回答 4

14

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

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

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

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

在此处输入图像描述

于 2013-07-08T12:25:25.510 回答
4

MemoryBarrier阻止重新排序,但此代码仍然损坏。

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

即使它目前以您期望的方式运行,在您测试的平台上,也不能保证它会继续这样做。唯一的保证是在与非托管代码互操作的上下文中(即 p/invoke、COM 互操作或 C++/CLI it-just-works)。

如果您想以可移植的面向未来的方式读取字节子集,则必须使用按位运算或字节数组和BitConverter. 即使语法不是那么好。

于 2013-07-08T13:12:11.430 回答
2

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

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

于 2013-07-08T12:48:18.720 回答
0

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

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

它并不意味着是最优雅的解决方案,并且可能不会通过大多数关于托管代码的公司政策,但它应该可以工作。

于 2013-07-08T13:12:45.357 回答