这是一个复杂的问题,在回答之前请仔细考虑。
考虑这种情况。两个线程(读取器和写入器)访问单个全局int
。这样安全吗?通常,我会毫不犹豫地回答,是的!
然而,在我看来,Herb Sutter并不这么认为。在他关于有效并发的文章中,他讨论了flawed lock-free queue和corrected version。
在第一篇文章的末尾和第二篇文章的开头,他讨论了很少考虑的变量特征,即写入顺序。 Int是原子的,很好,但是int不一定是有序的,它可以破坏任何无锁算法,包括我上面的场景。我完全同意, 保证 在现有和未来的所有平台上纠正多线程行为的唯一方法是使用原子(AKA内存屏障)或互斥锁。
我的问题;在实际硬件上写的重写是否有问题?还是多线程的偏执狂只是迂腐? 那么经典的单处理器系统呢? 那些更简单的RISC处理器如嵌入式power-pc?
澄清:我对Sutter先生关于硬件(处理器/缓存)重新排序变量写入的内容更感兴趣。我可以阻止优化器使用编译器开关破坏代码或手动检查汇编后编译。但是,我想知道硬件是否仍然可以在实践中搞乱代码。
答案 0 :(得分:26)
你检查装配的想法不够好;重新排序可以在硬件级别进行。
要回答你的问题“这在阅读硬件上是一个问题:”是的!事实上我自己遇到了这个问题。
可以解决单处理器系统或其他特殊情况下的问题吗?我认为“不”,因为从现在起五年后你可能需要在多核上运行,然后找到所有这些位置将是棘手的(不可能?)。
一个例外:为嵌入式硬件应用程序设计的软件确实可以完全控制硬件。事实上,我在这种情况下已经“欺骗”了这种情况。 ARM处理器。
答案 1 :(得分:9)
是的 - 使用内存屏障来防止在需要时重新排序指令。在一些C ++编译器中,volatile关键字已经扩展为每次读写都插入了隐式内存屏障 - 但这不是一个可移植的解决方案。 (与Interlocked * win32 API类似)。 Vista甚至添加了一些新的细粒度互锁API,允许您指定读或写语义。
不幸的是,C ++有一个松散的内存模型,任何类似的代码在某种程度上都是不可移植的,你必须为不同的平台编写不同的版本。
答案 2 :(得分:5)
就像你说的那样,由于在缓存或处理器级别重新排序,你实际上确实需要某种内存屏障来确保正确的同步,特别是对于多处理器(尤其是非x86平台)。 (我相信单处理器系统没有这些问题,但是不要引用我的话 - 我当然更倾向于安全地进行同步访问。)
答案 3 :(得分:5)
我们遇到了这个问题,尽管在Itanium处理器上,指令重新排序比x86 / x64更具攻击性。
修复是使用Interlocked指令,因为当时(当时)没有办法告诉编译器只是简单但是在赋值后写入屏障。
我们真的需要语言扩展来干净利落地处理这个问题。对于你试图从一段代码中尽可能多地提高性能的情况来说,使用volatile(如果编译器支持的话)太粗糙了。
答案 4 :(得分:4)
在真正的硬件上这是一个问题吗?
当然,特别是现在转向当前和未来CPU的多个内核。如果您依赖于有序的原子性来实现应用程序中的功能,并且您无法通过所选平台或使用同步原语来保证此要求,则在所有条件下,即客户从单个移动 - 核心CPU到多核CPU,那么你只是在等待问题发生。
引用Herb Sutter的文章(第二篇)
有序原子变量在流行的平台和环境中以不同的方式拼写。例如:
C#/ .NET中的
- Java中的
volatile
,与volatile int
中一样。volatile
或* Atomic *,如volatile int
,AtomicInteger
。atomic<T>
在C ++ 0x中,即即将推出的ISO C ++标准,与atomic<int>
中一样。
我还没有看到C ++ 0x如何实现有序原子性,所以我无法指定即将推出的语言功能是纯库实现还是依赖于语言的更改。您可以查看该提案,看看它是否可以作为非标准扩展程序合并到您当前的工具链中,直到新标准可用为止,甚至可能已经适用于您的情况。
答案 5 :(得分:3)
这是真实硬件上的问题。我的一个朋友为IBM工作,主要是通过在客户的代码中解决这类问题来谋生。
如果你想看看事情有多糟糕,可以在Java Memory Model(以及现在的C ++内存模型)上搜索学术论文。鉴于真实硬件可以做的重新排序,试图弄清楚高级语言中的安全性是一场噩梦。
答案 6 :(得分:2)
这不是安全的,并且存在显示此问题的真实硬件,例如xbox 360上的powerpc芯片中的内存模型允许重新排序写入。内在函数中缺乏障碍会加剧这种情况,请参阅msdn上的这篇文章了解更多详情。
答案 7 :(得分:1)
“这是安全的”问题的答案本质上是模棱两可的。
即使对于双打来说,它总是安全的,因为你的计算机不会着火。 这是安全的,从某种意义上说,你总是会获得过去某个时间int所持有的值, 它是不安全的,因为你可能得到一个值,它将被另一个线程更新。
“原子”意味着您获得第二个保证。由于double通常不是原子的,因此可以得到32个旧位和32个新位。这显然是不安全的。
答案 8 :(得分:1)
当我问到我对单处理器powerpc最感兴趣的问题时。其中一条评论InSciTek Jeff提到了powerpc SYNC和ISYNC指令。那些确定答案的关键。我在IBM的网站上找到了它here。
这篇文章很大而且非常密集,但带走的是不,这是不安全的。在旧的powerpc上,内存优化器不够复杂,不会在单处理器上造成问题。但是,较新的那些更具攻击性,甚至可以打破对全局int的简单访问。