【问题标题】:Memory barriers and large structs?内存屏障和大型结构?
【发布时间】:2009-11-13 17:30:21
【问题描述】:

假设我有一个由 100 个字节组成的结构。我对以下代码有什么保证?

m_myLargeStruct = someValue; // copying 100 bytes
Thread.MemoryBarrier();

// Executed by another thread, after "Thread.MemoryBarrier" was called by the first thread
Console.WriteLine(m_myLargeStruct.ToString());

内存模型是否保证放置内存屏障后 100 字节的复制将完成?还是内存屏障仅适用于处理器架构大小的类型? (32 位为 4 个字节,64 位为 8 个字节)。
这就是 volatile 关键字只适用于原始类型的原因吗? (如果我将一个 8 字节的成员声明为 volatile,这意味着将使用互锁的 instrinct 来更改它的值?[因为在 32 位机器上不能保证大于 4 字节的类型的原子性])。

我希望我足够清楚.. :)
谢谢

【问题讨论】:

    标签: c# multithreading volatile memory-model memory-barriers


    【解决方案1】:

    除非阅读线程也有内存障碍,否则我不认为它会对你有太大帮助。

    我个人会回避:

    • 那么大的结构
    • 深入了解内存模型以编写无锁代码

    ...除非您有非常重要的理由这样做。 非常很难对可变数据进行无锁编码;我相信即使是专家也很挣扎。我通常发现“对访问数据的每个块进行锁定”方法更容易正确使用,并且在 99% 的情况下性能良好。

    我相信 Microsoft 的 PFX 团队能够正确地编写无锁编码,并且他们会为我提供使用他们的代码相对轻松地编写自己的无锁程序的方法。我不相信自己能把这种事情做好。如果我需要明确使用内存屏障,那可能意味着我太努力了。

    【讨论】:

    • 一个简单的仿射矩阵可能有这么大——核心框架为此使用结构。当然,结构的实现似乎不是很完美,所以除非你愿意真正编写隐含地解决编译器/CLR 问题的代码,否则你不太可能获得更好的性能。另一方面,在许多方面,结构比类具有更好的语义。我可以很好地想象使用结构只是为了具有良好而简单的值语义。
    • @Eamon:你不需要结构来获得值语义,你只需要一个不可变的类。以String类为例。
    • 我问的是学术意义上的......我并没有真正的 100 字节作为结构。我要特别问的是保证什么,以及如何保证。这个问题不是关于“软件开发的一般良好实践”......
    • @Jon “除非读取线程也有内存屏障” - 在这种情况下你也没有任何保证 - 因为读取线程可以在写入线程之前执行(即使没有 - 内存屏障防止从 single 线程而不是整个架构的角度重新排序)。他显然需要某种形式的同步(最好锁定,除非通过大量分析证明不合适)
    【解决方案2】:

    在第二个线程中,在 WriteLine 之前,您需要另一个内存屏障。 (如果你的系统提供了非对称内存屏障,那么在赋值之后执行一个 Release 屏障,在 WriteLine 之前执行一个 Acquire 屏障就足够了。

    数据大小无关紧要。

    【讨论】:

    【解决方案3】:

    很明显,答案是,或者更确切地说,您无法保证任何事情。没有什么可以阻止操作系统在启动打印出 100 字节结构的线程之前换出正在写入 100 字节结构的线程。

    当您想通过标志或其他原子值协调对数据的访问时,使用内存屏障。我不知道你到底想做什么,所以我不能给你很好的示例代码来说明你应该怎么做。

    【讨论】:

    • 在仔细阅读了 Duffy 的书后,我意识到这整个问题都是由于我的一个误解,谢谢
    【解决方案4】:

    您在两个地方/线程都需要一个内存屏障,当然您需要在两者之间进行某种同步,这样第二个线程的屏障不会在第一个线程之前“运行”。

    具体来说,写入线程需要一个“释放”内存屏障,而读取线程需要一个“获取”内存屏障(如果底层平台支持单独的屏障语义)。

    除非您是出于学术好奇心或正在编写自己的框架,否则您应该只使用库/框架/平台中的同步对象。试图让所有这些东西都正确是很棘手的,而且它已经在提供的同步对象中完成了。

    【讨论】:

      【解决方案5】:

      嗯,首先你不应该有这么大的结构。除非你对如何使用结构非常小心,否则它会比使用类慢。此外,它与结构的值语义有悖常理。

      也就是说,内存屏障将保证结构被复制。优化不会将任何指令移过障碍。

      volatile 关键字有点不同。它保证没有针对变量的操作被优化掉,并且确实保证了内存访问的顺序。但是,对于不能以原子方式访问的数据类型,它对于线程用途几乎没有用处,因为您仍然可以读取一半的新值和一半的旧值。

      【讨论】:

      • 不,volatile 确实保证订购。写入 volatile 变量具有“释放”语义,而从一个变量读取具有“获取”语义 IIRC。 (有关详细信息,请参阅 ECMA 335。)
      猜你喜欢
      • 1970-01-01
      • 2011-07-05
      • 1970-01-01
      • 2012-07-02
      • 2014-09-11
      • 2014-02-04
      • 1970-01-01
      • 2011-09-27
      • 1970-01-01
      相关资源
      最近更新 更多