【问题标题】:One reader one writer, int or atomic_int一位读者一位作者,int 或 atomic_int
【发布时间】:2016-06-02 21:40:52
【问题描述】:

我知道这不是一个新问题,但在阅读了有关 c++11 内存栅栏后我感到困惑;

如果我有一个读者线程和一个作者线程。
可以用普通的int吗?

    int x = 0; // global
writer    reader
x = 1;    printf("%d\n", x);

这种行为是否未定义?
我可以在阅读器线程中获得未定义的值吗?
或者就像使用std::atomic_uint_fast32_tstd::atomic<int>?因此,该值最终会到达阅读器线程。

    std::atomic<int x = 0; // global
writer                                    reader
x.store(1, std::memory_order_relaxed);    printf("%d\n", x.load(std::memory_order_relaxed));

答案是否取决于我使用的平台? (例如 x86),那么加载/存储普通的int 是一条 CPU 指令?

如果两种行为相似,我是否应该期望两种类型的性能相同?

【问题讨论】:

    标签: c++ multithreading c++11 atomic memory-fences


    【解决方案1】:

    简而言之,切勿在多线程环境中使用普通的int 进行共享。

    问题不仅在于您的 CPU,还在于您的编译器的优化器。 gcc 可以(并且将会)优化如下代码:

    while(i == 1) {} 转换为 if(i==1) { while(1) {} }。一旦它检查了变量一次,它就不必再次重新加载该值。这与所有其他可能的问题是分开的,即看到一半写入的值(实际上通常不会出现在 x86 整数上)。

    测量atomic 的效果非常困难——在许多情况下,CPU 可以高度优化访问,而在其他情况下,它们的速度要慢得多。你真的必须在实践中进行基准测试。

    【讨论】:

    • "问题不只是你的 CPU"
    • 如果有另一个线程调用的i = 0这样的代码,它真的可以优化while(i == 1) {} into if(i==1) { while(1) {} }吗?难道它不知道,所以while 的例子无效?
    • 是的,如果 i 是一个普通整数。因为在一个线程中读取int 并将其写入另一个线程是未定义的行为。如果编译器必须假设变量可以随时在其他线程中写入,那么他们实际上必须编译所有变量,例如 atomic 变量,这会使代码变慢。
    【解决方案2】:

    使用原子对编译器和 CPU 级别都有影响。正如 cmets 建议的那样,您应该始终使用原子,否则编译器和 CPU 将合谋对您的代码进行疯狂且不直观的转换,使其无法按照您的合理预期进行。

    你的问题的第二部分更微妙——使用原子而不是裸 int 的惩罚是什么?这当然非常依赖于编译器和 CPU,但是让我们暂时假设您的编译器是“智能的”并且您使用的是 Intel CPU。智能,我的意思是它不只是将您的所有访问都包装在一个互斥块中,这当然满足原子的所有要求,但在性能上可能不是最佳的。在 Intel CPU 上,你有一些关于存储/加载可见性的内置保证,这使得编译器更容易在没有特殊指令的情况下做正确的事情——他们只需要不优化“正常”行为IA64 memory ordering。虽然这并不涵盖所有情况,但它确实处理了您的“宽松一致性”情况。欲了解更多信息,请参阅memory fencing instructions

    对于您具有宽松一致性的情况,英特尔没有 CPU 级别的惩罚,因为不需要生成栅栏指令。大多数情况下,当您需要更强的一致性时(例如在实现自旋锁或制作发布顺序很重要的无锁算法时),就会出现惩罚。这些将导致互锁指令、栅栏指令或总线锁定前缀,这可能会产生重大影响。

    【讨论】:

    猜你喜欢
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多