一位 reader 一位作家,int 或 atomic_int
One reader one writer, int or atomic_int
我知道这不是一个新问题,但在阅读有关 c++11 内存栅栏的内容后我感到困惑;
如果我有一个 reader 线程和一个作者线程。
可以用普通的int
吗?
int x = 0; // global
writer reader
x = 1; printf("%d\n", x);
此行为是否未定义?
我可以在 reader 线程中得到一个未定义的值吗?
或者它就像使用 std::atomic_uint_fast32_t
或 std::atomic<int>
?因此,该值最终将到达 reader 线程。
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),所以 loading/storing 一个普通的 int
是一个 CPU 指令?
如果两种行为相似,我是否应该期望两种类型的性能相同?
简而言之,切勿在多线程环境中使用普通 int
共享。
问题不仅在于您的 CPU,还在于您的编译器优化器。 gcc 可以(并且将会)优化代码,例如:
while(i == 1) {}
变成 if(i==1) { while(1) {} }
。一旦它检查了一次变量,就不必再次重新加载该值。这与所有其他可能的问题不同,即看到写了一半的值(实际上通常不会发生在 x86 整数上)。
衡量 atomic
的效果非常困难——在许多情况下 CPUs 可以高度优化访问,而在其他情况下它们要慢得多。你真的必须在实践中进行基准测试。
使用原子在编译器和 CPU 级别都有影响。正如评论所建议的那样,您应该始终使用原子,否则编译器和 CPU 将合谋对您的代码进行疯狂且不直观的转换,使其无法按照您的合理预期进行。
你的问题的第二部分更微妙——使用 atomic 而不是 naked int 的惩罚是什么?这当然非常依赖于编译器和 CPU,但让我们暂时假设您的编译器是 "smart" 而您使用的是 Intel CPU。所谓智能,我的意思是它不只是将您的所有访问都包装在一个互斥块中,这当然满足原子的所有要求,但在性能上会是次优的。在 Intel CPUs 上,你有一些关于 store/load 可见性的内置保证,这使得编译器更容易在没有特殊指令的情况下做正确的事情——他们只需要不优化 "normal" 行为 IA64 memory ordering. While this doesn't cover all cases, it does deal with your "relaxed consistency" case. For more info see memory fencing instructions
对于你的宽松一致性的情况,英特尔没有CPU级的惩罚,因为不需要生成栅栏指令。大多数情况下,当您需要更强的一致性时(例如,在实现自旋锁或制作发布顺序很重要的无锁算法时),惩罚就会出现。这些将导致互锁指令、栅栏指令或总线锁定前缀,这可能会产生重大后果。
我知道这不是一个新问题,但在阅读有关 c++11 内存栅栏的内容后我感到困惑;
如果我有一个 reader 线程和一个作者线程。
可以用普通的int
吗?
int x = 0; // global
writer reader
x = 1; printf("%d\n", x);
此行为是否未定义?
我可以在 reader 线程中得到一个未定义的值吗?
或者它就像使用 std::atomic_uint_fast32_t
或 std::atomic<int>
?因此,该值最终将到达 reader 线程。
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),所以 loading/storing 一个普通的 int
是一个 CPU 指令?
如果两种行为相似,我是否应该期望两种类型的性能相同?
简而言之,切勿在多线程环境中使用普通 int
共享。
问题不仅在于您的 CPU,还在于您的编译器优化器。 gcc 可以(并且将会)优化代码,例如:
while(i == 1) {}
变成 if(i==1) { while(1) {} }
。一旦它检查了一次变量,就不必再次重新加载该值。这与所有其他可能的问题不同,即看到写了一半的值(实际上通常不会发生在 x86 整数上)。
衡量 atomic
的效果非常困难——在许多情况下 CPUs 可以高度优化访问,而在其他情况下它们要慢得多。你真的必须在实践中进行基准测试。
使用原子在编译器和 CPU 级别都有影响。正如评论所建议的那样,您应该始终使用原子,否则编译器和 CPU 将合谋对您的代码进行疯狂且不直观的转换,使其无法按照您的合理预期进行。
你的问题的第二部分更微妙——使用 atomic 而不是 naked int 的惩罚是什么?这当然非常依赖于编译器和 CPU,但让我们暂时假设您的编译器是 "smart" 而您使用的是 Intel CPU。所谓智能,我的意思是它不只是将您的所有访问都包装在一个互斥块中,这当然满足原子的所有要求,但在性能上会是次优的。在 Intel CPUs 上,你有一些关于 store/load 可见性的内置保证,这使得编译器更容易在没有特殊指令的情况下做正确的事情——他们只需要不优化 "normal" 行为 IA64 memory ordering. While this doesn't cover all cases, it does deal with your "relaxed consistency" case. For more info see memory fencing instructions
对于你的宽松一致性的情况,英特尔没有CPU级的惩罚,因为不需要生成栅栏指令。大多数情况下,当您需要更强的一致性时(例如,在实现自旋锁或制作发布顺序很重要的无锁算法时),惩罚就会出现。这些将导致互锁指令、栅栏指令或总线锁定前缀,这可能会产生重大后果。