程序并行下的效率与安全是通过锁(互斥量、自旋锁)和lock-free(原子操作)方式来实现的,由于mutex修试下的临界区是串行的,首先记录下lock-free下的安全问题。
一、基础知识
1、cache line
叫做缓存行,是指cpu从内存中读取数据时总要把目标数据周围的一部分数据都读进来(局部一致性),这样一次读取的一组数据就是cache line,是缓存交换中的最小单位,不同机器存在差别。
cache line被读取进来后会拷贝到内核私有的L1/L2 cache下,当原子变量被修改后,会通知cpu的其他核心来同步变量所在的cache line(cpu一致性同步)。这就存在false sharing现象:当某个变量被多个核心共享时,一个核心对其进行修改就会导致其所在的cache line失效,需要重新从内存读取, cache line中的其他变量也要等待缓存同步而变慢,严重影响整体性能。因此并发编程中为避免这种情况:
- 频繁被修改的变量要放到单独的cache line中,按cache line对齐。读远大于写的共享变量放到其他cache line里。
- 对于计数器,被多个线程频繁修改,如果只是用作日志打印之类完全可以用thread local类型,日志打印时再合并变量值,性能能可能有几十倍的差距。
当然cache line同步是当共享变量能被其他核心可见的情况下,比如说全局的原子变量或者volatile修饰的变量。
2、内存序模型 Memoty Fence
原子操作只保证了对变量的访问控制,但是仍存在两种因素导致多线程代码运行结果不符合预期:
-为了提高运行效率,在每个机器周期内运行更多的指令,编译器和CPU会对指令进行重排。原则是单线程下互不依赖的指令进行重排,重拍后单线程下运行没有问题,多线程下就变的不确定了。
//Thread-1 //Thread-2
p.init(); //A if (a) //C
a = true; //B p.run(); //D
由于单个线程下p和a是不依赖的,所以Thread-1/Thread-2重排后完全可能变成B->C->D->A,这样p就会在为初始化情况下执行,不符合预期。
- 即使没有重排,p和a的值会在Thread-1执行后独立的同步到Thread-2所在核心的cache,Thread-2仍有可能在看到a==true时看到的是为初始化的p的值。
对解决指令重排,c++提供了6中内存序模型,常用的是Release/Acquire组合,write-release和read-acquire配对使用。详见下面例子。
memory order | 作用 |
---|---|
memory_order_relaxed | 没有fencing作用, 原子操作默认使用。 |
memory_order_consume | 后面依赖此原子变量的访存指令勿重排至此条指令之前 |
memory_order_acquire | 后面访存指令勿重排至此条指令之前 |
memory_order_release | 前面访存指令勿重排至此条指令之后。当此条指令的结果对其他线程可见后,之前的所有指令都可见 |
memory_order_acq_rel | acquire + release语意 |
memory_order_seq_cst | acq_rel语意外加所有使用seq_cst的指令有严格地全序关系 |
上面的例子更正为:
//Thread-1
p.init(); //A
a.store(true, std::memory_order_release); //B
//Thread-2
if (a.load(std::memory_order_acquire)) //C
p.run(); //D
B指令的write-release保证了A->B,C指令的read-acquire保证了C->D,然后配对使用这一组会先执行write再执行read,就保证了A->B->C->D的执行顺序。
对于cache line的同步问题(可见性),memory fence只能保证可见性指令的顺序性,不能保证指令的可见性,详见:https://github.com/apache/incubator-brpc/blob/master/docs/cn/atomic_instructions.md#memory-fence
参考:
【1】https://github.com/apache/incubator-brpc/blob/master/docs/cn/atomic_instructions.md#memory-fence
【2】https://aaron-ai.com/docs/memory_reordering_simple_analysis/
【3】https://www.boost.org/doc/libs/1_56_0/doc/html/atomic/usage_examples.html
【4】https://www.codedump.info/post/20191214-cxx11-memory-model-2/