知乎关于atomic、volatile、缓存一致性的讨论:
https://www.zhihu.com/question/265714945
内存模型Memory Model: 从多处理器到高级语言:
https://github.com/GHScan/TechNotes/blob/master/2017/Memory_Model.md
CPU Cache的设计,内存屏障的原理和用法,内存一致性(最有用的一集)
https://wudaijun.com/2019/04/cache-coherence-and-memory-consistency/
http://www.rdrop.com/~paulmck/scalability/paper/whymb.2010.06.07c.pdf
对开发者而言,编写并发程序需要理解三个东西: 原子性,可见性和顺序性。
原子性: 尽管在如今大部分平台下,对一个字的数据进行存取(int,指针)的操作本身就是原子性的,但为了更好地跨平台性,通过atomic操作来实现原子性是更好的方法,并且不会造成额外的开销。C++的atomic还提供可见性和顺序性选项
可见性: 数据同步相关,前面讨论的CPU Cache设计主要关注的就是可见性,即每个读操作所返回的值必须是最后一次对该存储位置的写操作的值。Cache一致性主要解决的就是数据可见性的问题
顺序性: 内存屏障的另一个功能就是可以限制局部的指令重排(一些文章将内存屏障定义为限制指令重排工具,我认为是不准确的,如前面所讨论的,即使没有指令重排,有时也需要内存屏障来保证可见性)。内存屏障保证屏障前的某些操作必定限于屏障后的操作发生且可见。但屏障前或屏障后的指令,CPU/编译器仍然可以在不改变单线程结果的情况下进行局部重排。每个硬件平台有自己的基础内存一致性(强/弱内存模型)
C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI:
https://blog.csdn.net/xubuwei/article/details/98090701
其中提到,
volatile注意事项:
1.与平台无关的多线程程序,volatile几乎无用(Java和C#中的volatile除外);
2.volatile不保证原子性(一般需使用CPU提供的LOCK指令);
3.volatile不保证执行顺序;
4.volatile不提供内存屏障(Memory Barrier)和内存栅栏(Memory Fence);
5.多核环境中内存的可见性和CPU执行顺序不能通过volatile来保障,而是依赖于CPU内存屏障。
注:volatile诞生于单CPU核心时代,为保持兼容,一直只是针对编译器的,对CPU无影响。
volatile在C/C++中的作用:
1.告诉编译器不要将定义的变量优化掉;
2.告诉编译器总是从内存地址中取被修饰的变量的值,而不是寄存器取值。
C++ atomic的内存序:
https://www.sidney.wiki/cpp/952
内存栅栏、内存屏障:
atomic的std::atomic_load
std::atomic_store
std::atomic_exchange
std::atomic_compare_exchange_weak
std::atomic_compare_exchange_strong
以上函数都是只针对原子变量,而栅栏是对所有数据有效。
栅栏是一种同步原语,它可以用来同步多个线程之间的操作。栅栏可以将线程分为若干个阶段,在每个阶段中,线程需要等待其他线程完成特定的操作后才能继续执行下一步操作。栅栏的主要作用是协调多个线程之间的操作,确保它们按照预期的顺序执行。
在C++中,栅栏有多种实现,包括std::atomic_thread_fence
、std::atomic_signal_fence
、std::thread::join
等。其中,std::atomic_thread_fence
和std::atomic_signal_fence
是用于控制内存访问顺序的栅栏,std::thread::join
是用于等待其他线程完成的栅栏。
下面是std::atomic_thread_fence的用法示例:
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
x.store(true,std::memory_order_relaxed); // 1
std::atomic_thread_fence(std::memory_order_release); // 2 栅栏也要指向如何限制内存序
y.store(true,std::memory_order_relaxed); // 3
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); // 4
std::atomic_thread_fence(std::memory_order_acquire); // 5 保证之的读操作能读到最新数据
if(x.load(std::memory_order_relaxed)) // 6
++z;
}
void write_y_then_x()
{
y.store(true, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release); // 7
int r = x.load(std::memory_order_relaxed);
}
在使用栅栏的情况下原子变量就不需要指定内存序列了,可以改成memory_order_relaxed松散内存序。
注意栅栏的有效范围是前后紧邻的一行语句,如下代码中后面两行的语句顺序无法保证:
void write_x_then_y()
{
std::atomic_thread_fence(std::memory_order_release);
x.store(true,std::memory_order_relaxed);
y.store(true,std::memory_order_relaxed);
}