C++编译器如何支持C++11原子操作,但不支持C++11内存模型?

32
在查看Clang和g++ C++11实现状态时,我注意到了一些奇怪的事情:
它们支持C++11原子性,但不支持C++11内存模型。
我印象中您必须拥有C++11内存模型才能使用原子性。那么支持原子性和内存模型之间究竟有什么区别?
缺乏内存模型支持是否意味着使用std::atomic<T>的合法C++11程序不具备seq consistent性?
参考资料:
http://clang.llvm.org/cxx_status.html
http://gcc.gnu.org/gcc-4.7/cxx0x_status.html

原子性是内存模型的三个属性之一,另外两个是内存可见性和内存排序。原子性不是内存模型的“同义词”。 - mloskot
3个回答

15

其中一个问题是"内存位置"的定义,它允许(并强制编译器支持)使用不同的锁锁定不同的结构成员。这篇讨论介绍了由此引起的RL问题。

基本上问题在于如下定义的struct

struct x {
    long a;
    unsigned int b1;
    unsigned int b2:1;
};
编译器可以通过覆盖b1来实现对b2的写入(从报告中可以看出编译器确实这样做)。因此,这两个字段必须作为一个整体进行锁定。但是,由于C++11内存模型的影响,这是被禁止的(好吧,不是真正被禁止,但编译器必须确保对b1b2的同时更新不会相互干扰;它可以通过锁定或CAS方式对每个此类更新执行操作,有些架构上的生活确实很困难)。引用自报告:

我向我们的GCC团队提出了这个问题,他们对我说:"C没有提供这样的保证,如果共享天然对齐的字长存储区域,也无法可靠地使用不同的锁锁定不同的结构字段。C ++11内存模型将保证这一点,但这不是实现,你也不会使用C ++11编译器构建内核。 "

维基中还可以找到其他有用信息。


对我来说太技术化了,但这是不错的信息:我们提到C11/C++11内存模型的原因是因为我们计划在某些选项请求时支持它,可能是在GCC 4.8中(在选择这些标准时默认启用?或者在明确请求时)。这甚至会禁用一些优化,这些优化对于单线程应用程序来说很好,但对于这些内存模型、OpenMP或许多其他线程程序来说并不好。例如,如果不能保证变量始终存储在循环中,则循环存储运动就不好。 - NoSenseEtAl
int x;void foo (int j) { int i; for (i = 0; i < 100000; i++) if (i > j) x = i; } 不能执行,因为它会在原本不涉及该变量的代码中引入 tmp = x; ... x = tmp;,如果其他线程修改了 x,它可能会有意外的值。Jakub - NoSenseEtAl
酷编辑(链接很棒),我常常想知道那些制造神奇事物(编译器)的人住在哪里,为什么他们对分享信息如此害羞。:) 事实是大多数时候信息都在那里,但不容易通过谷歌搜索获得 :) - NoSenseEtAl
这个答案是否还准确?如果GCC仍然不支持这个功能,我会感到惊讶的... - user541686

12
我猜在这些情况下,“缺少内存模型”只是意味着优化器是在C++11内存模型发布之前编写的,可能会执行无效的优化。验证优化是否符合内存模型非常困难和耗时,所以不足为奇clang / gcc团队尚未完成。

缺乏内存模型支持是否意味着使用std :: atomic的合法C ++ 11程序不是seq一致的?

是的,这是一个可能性。更糟糕的是:编译器可能会在(根据C ++11标准)无竞争的程序中引入数据竞争,例如通过引入推测写入。

例如,几个C ++编译器曾经执行过此优化:

for (p = q; p = p -> next; ++p) {
    if (p -> data > 0) ++count;
}

可以进行优化,使其变得更高效:

register int r1 = count;
for (p = q; p = p -> next; ++p) {
    if (p -> data > 0) ++r1;
}
count = r1;

如果所有的p->data都是非负数,那么原始源代码没有写入到count中,但优化后的代码会这样做。这可能会在本来无竞争的程序中引入数据竞争,因此C++11规范不允许这样的优化。现有编译器现在必须验证(并在必要时进行调整)所有优化。

详情请参见并发内存模型编译器后果


你知道什么很有趣吗?MS表示对于VC++ 11:内存模型:N2429使核心语言认识到了多线程的存在,但似乎对于编译器实现并没有什么要做的(至少对于已经支持多线程的编译器来说如此)。因此在表格中它是无关项。 - NoSenseEtAl
@NoSenseEtAl:那只是整个内存模型中非常特定的一部分。同样的表格还有“数据依赖性排序”(N2664),这需要工作。通常,我们也会将其视为C++11内存模型的一部分。 - MSalters
好的..所以他们很懒。这正是我想到的。 - user2074102

0

问题不在于它们不支持内存模型,而是它们(尚)不支持标准API与内存模型进行交互。该API包括许多互斥锁。

然而,Clang和GCC一直尽可能地考虑线程安全,即使没有正式的标准。您不必担心优化将事物移动到原子操作的错误侧面。


1
那个回答真的很不令人满意;问题中链接的功能列表特别讨论了内存模型(及其提议),而不是任何API、库、互斥锁等。此外,至少gcc 4.7 *在库中确实有互斥锁。像“尽可能具备线程感知能力而没有正式标准”这样的说法只是胡说八道;它并没有任何意义。线程感知并不能告诉你编译器做了什么,肯定还有更多内容涉及到“将事物移动到原子操作的错误侧面”。 - jpalecek
1
@jpalecek,虽然我同意那个答案不是很好,但没有必要说废话。 :) 是的,互斥锁至少从g++ 4.6开始就有了,尽管如果我记得正确,atomic_thread_fence还没有。 :) 现在无法检查(我正在使用VS)。 - NoSenseEtAl
@NoSenseEtAl:atomic_thread_fence在gcc-4.7中。我使用BS一词来指代模糊的营销用语,比如“本地支持...”,它们似乎很重要,但经过一点思考,它们并没有什么意义。 - jpalecek
Clang和GCC链接都指向http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2429.htm,其中包括内存模型规则(例如,“什么是竞争?”)以及可以视为使用该模型的API(例如,“库定义了许多原子操作(第29条[原子])和锁定操作(第30条[线程]),这些操作被特别标识为同步操作。这些操作在使一个线程中的赋值对另一个线程可见方面发挥着特殊作用。”)。 - Max Lybbert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接