无锁编程那些事儿读书笔记1
参考资料
http://chonghw.github.io/
http://chonghw.github.io/blog/2016/08/11/memoryreorder/
http://chonghw.github.io/blog/2016/09/19/sourcecontrol/
http://chonghw.github.io/blog/2016/09/28/acquireandrelease/
http://www.wowotech.net/kernel_synchronization/Why-Memory-Barriers.html
http://www.wowotech.net/kernel_synchronization/why-memory-barrier-2.html
http://www.wowotech.net/kernel_synchronization/memory-barrier-1.html
http://www.wowotech.net/kernel_synchronization/perfbook-memory-barrier-2.html
https://kukuruku.co/post/lock-free-data-structures-introduction/
https://kukuruku.co/post/lock-free-data-structures-basics-atomicity-and-atomic-primitives/
https://kukuruku.co/post/lock-free-data-structures-the-inside-memory-management-schemes/
文章出处
那么什么情况下,我们需要对共享变量的操作采用原子操作呢
通常情况下,我们有一个对共享变量必须使用原子操作的规则:
任何时刻,只要存在两个或多个线程并发地对同一个共享变量进行操作,并且这些操作中的其中一个是执行了写操作,那么所有的线程都必须使用原子操作。
如果违反上面的规则,即存在某个线程使用了非原子操作,那么你将会陷入一个在C++11标准中称之为数据竞争(data race)(这里的数据竞争和Java中的data race概念,以及更通用的race condition是不一样的)的情形。如果你引发了数据竞争,那么就会得到一个"未定义行为(undefined behavior)"的结果,它们会导致torn reads(撕裂读)和torn writes(撕裂写),也就是一个非完整的读写。
C++中volatile具有以下特性
1. 易变性:所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。
2. "不可优化"性:volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。
3. "顺序性":能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。Volatile变量与非Volatile变量的顺序,编译器不保证顺序,可能会进行乱序优化。
lock-free条件
1. The compiler is a recent version MSVC, GCC or Clang.
2. The target processor is x86, x64 or ARMv7 (and possibly others).
3. The atomic type is std::atomic<uint32_t>, std::atomic<uint64_t> or std::atomic<T*> for some type T.
Weak and Strong CAS
相信大家看到C++11的CAS操作有两个compare_exchange_weak和compare_exchange_strong,CAS怎么还有强弱之分呢?现代处理器架构对CAS的实现分成两大阵营:(1)实现了原子性CAS原语 -- X86、Intel Itanium、Sparc等处理器架构,最早实现于IBM System 370。(2)实现LL/SC对(load-linked/store-conditional) -- PowerPC, MIPS, Alpha, ARM 等处理器架构,最早实现于DEC,通过LL/SC对可以实现原子性CAS,但在一些情况下它并不具有原子性。为什么会存在LL/SC对的使用,而不直接实现CAS原语呢?要说明LL/SC对存在的原因,不得不说一下无锁编程中的一个棘手问题:ABA问题。
Load-Linked / Store-Conditional -- LL/SC对
ABA问题的本质在于,CAS进行比较的是指针指向的内存地址,。如果处理器能够感知得到在进行CAS的内存地址的内如发生了变化,让CAS失败的话,那么就能从源头上解决ABA问题。于是PowerPC, MIPS, Alpha, ARM 等处理器架构的开发人员找到了load-linked、store-conditional (LL/SC) 这样的操作对来彻底解决ABA问题,伪代码如下:
word LL( word * pAddr )
{
return *pAddr ;
}
bool SC( word * pAddr, word New )
{
if ( data in pAddr has not been changed since the LL call)
{
*pAddr = New ;
return true ;
}
else
return false ;
}
何种情形下使用Weak CAS,何种情形下使用Strong CAS呢
倘若CAS在循环中(这是一种基本的CAS应用模式),循环中不存在成千上万的运算(循环体是轻量级和简单的,本例的无锁堆栈),使用compare_exchange_weak。否则,采用强类型的
内存安全回收
内存安全回收问题根源上是待回收的内存还被其他线程引用中,此时如果delete该内存,那么引用该内存的线程就会出现使用非法内存的问题,那么我们只能延迟回收该内存,即在安全时刻再delete。目前用于lock free代码的内存回收的经典方法有:Lock Free Reference Counting、Hazard Pointer、Epoch Based Reclamation、Quiescent State Based Reclamation等。