无锁编程技术
1.基于锁的编程的缺点
多线程编程是多CPU系统在中应用最广泛的一种编程方式,在传统的多线程编程中,多线程之间一般用各种锁的机制来保证正确的对共享资源(share resources)进行访问和操作。在多线程编程中只要需要共享某些数据,就应当将对它的访问串行化。比如像++count(count是整型变量)这样的简单操作也得加锁,因为即便是增量操作这样的操作,,实际上也是分三步进行的:读、改、写(回)。movl x, %eaxaddl $1, %eaxmovl %eax, x 更进一步,甚至内存变量的赋值操作都不能保证是原子的,比如在32位环境下运行这样的函数 void setValue(){value = 0x100000006; }执行的过程中,这两条指令之间也是可以被打断的,而不是一条原子操作。(也就是所谓的写撕裂)所以修改共享数据的操作必须以原子操作的形式出现,这样才能保证没有其它线程能在中途插一脚来破坏相应数据。而在使用锁机制的过程中,即便在锁的粒度(granularity),负载(overhead),竞争(contention),死锁(deadlock)等需要重点控制的方面解决的很好,也无法彻底避免这种机制的如下一些缺点:
1, 锁机制会引起线程的阻塞(block),对于没有能占用到锁的线程或者进程,将一直等待到锁的占有者释放锁资源后才能继续执行,而等待时间理论上是不可设置和预估的。
2, 申请和释放锁的操作,增加了很多访问共享资源的消耗,尤其是在锁竞争(lock-contention)很严重的时候,比如这篇文章所说:http://preshing.com/20111118/locks-arent-slow-lock-contention-is/。
3, 现有实现的各种锁机制,都不能很好的避免编程开发者设计实现的程序出现死锁或者活锁的可能。
4, 优先级反转(prorithy inversion)和锁护送(Convoying)的现象。
5, 难以调试,无锁编程(Lock-Free)就是在某些应用场景和领域下解决以上基于锁机制的并发编程的一种方案。
2.无锁编程(LOCK-FREE)的定义
提到无锁编程(lock-free),按字面最直观的理解是不使用锁的情况下实现多线程之间对变量同步和访问的一种程序设计实现方案。严格的说这个理解是不对的,Lock-Free的程序肯定是不包括锁机制的,而不包括锁机制的程序不一定是lock-free的。更准确的说,在并发编程上按照同步的维护划分,可以分为阻塞的编程方式(Block)和非阻塞的编程方式(Non-blocking Synchronization)。阻塞的编程方式基本是基于锁的(lock-based)。 其中无锁编程(Lock-free)属于非阻塞同步(Non-blocking Synchronization)中的一种情况,实现非阻塞同步的算法方案按照效果要求不同可以粗略的分为:Wait-free: 满足等待无关的程序,任何线程可以在有限步之内结束,不管其它线程的执行速度和进度如何。Lock-free:锁无关的程序,一个锁无关的程序能够确保它所有线程中至少有一个能够继续往下执行,而有些线程可能会被的延迟。然而在整体上,在某个时刻至少有一个线程能够执行下去。作为整体进程总是在前进的,尽管有些线程的进度可能没有其它线程进行的快。Obstruction-free:在任何时间点,一个孤立运行线程的每一个操作可以在有限步之内结束。只要没有竞争,线程就可以持续运行。一旦共享数据被修改,Obstruction-free 要求中止已经完成的部分操作进行回滚。更细致的还可以把并发编程按效果划分为:
Blocking
1. Blocking
2. Starvation-Free
Obstruction-Free
3. Obstruction-Free
Lock-Free
4. Lock-Free (LF)
Wait-Free
5. Wait-Free (WF)
6. Wait-Free Bounded (WFB)
7. Wait-Free Population Oblivious (WFPO)
具体细节可以参考这篇文章http://ifeve.com/lock-free-and-wait-free/,有对阻塞非阻塞的等定义的详细描述,这里不详细论述。
3.无锁编程中涉及的一些技术原理
无锁编程具体使用和考虑到的技术方法包括:原子操作(atomic operations), 内存栅栏(memory barriers), 内存顺序冲突(memory order), 指令序列一致性(sequential consistency)和顺ABA现象等等,这方面借用一篇资料的总结的图概况:
在这其中最基础最重要的是操作的原子性或说原子操作。原子操作可以理解为在执行完毕之前不会被任何其它任务或事件中断的一系列操作。原子操作是非阻塞编程最核心基本的部分,没有原子操作的话,操作会因为中断异常等各种原因引起数据状态的不一致从而影响到程序的正确。对于原子操作的实现机制,在硬件层面上CPU处理器会默认保证基本的内存操作的原子性,CPU保证从系统内存当中读取或者写入一个字节的行为肯定是原子的,当一个处理器读取一个字节时,其他CPU处理器不能访问这个字节的内存地址。但是对于复杂的内存操作CPU处理器不能自动保证其原子性,比如跨总线宽度或者跨多个缓存行(Cache Line),跨页表的访问等。这个时候就需要用到CPU指令集中设计的原子操作指令,现在大部分CPU指令集都会支持一系列的原子操作。而在无锁编程中经常用到的原子操作是Read-Modify-Write (RMW)这种类型的,这其中最常用的原子操作又是 COMPARE AND SWAP(CAS),几乎所有的CPU指令集都支持CAS的原子操作,比如X86平台下中的是 CMPXCHG。继续说一下CAS,CAS操作行为是比较某个内存地址处的内容是否和期望值一致,如果一致则将该地址处的数值替换为一个新值。CAS能够操作的位数越多,使用它来实现锁无关的数据结构就越容易(细节可以在intel手册中查看)。CAS操作具体的实现原理主要是两种方式:总线锁定和缓存锁定。所谓总线锁定,就是CPU执行某条指令的时候先锁住数据总线的, 使用同一条数据总线的CPU就无法访问内存了,在指令执行完成后再释放锁住的数据总线。锁住数据总线的方式系统开销很大,限制了访问内存的效率,所以又有了基于CPU缓存一致性来保持操作原子性作的方法作为补充,简单来说就是用CPU的缓存一致性的机制来防止内存区域的数据被两个以上的处理器修改(可详见CPU缓存的MESI协议)。在操作系统的层面,Linux系统提供了软件级的原子操作,包括两大类系统调用,一类是基于对整数进行操作的atomic_set/and/inc,一类是针对单独的位进行操作的set/clear/change_bit,它们大部分都是基于硬件层面的CAS的指令实现的。在各种开发语言中(c,c++,java)基于操作系统提供的接口也都封装实现了对应的原子操作api,所以开发者完全可以直接调用各个开发语言提供的接口实现无锁程序。除了使用原子操作保证操作的原子性,还要考虑在不用的语言和内存模型下,如何保证,操作的顺序性, 编译时和运行时的指令重排序,还有由假共享引起内存顺序冲突(Memory order violation)等等细节。原子性确保指令执行期间不被打断,要么全部执行,要么根本不执行,而顺序性确保即使两条或多条指令出现在独立的执行线程中或者独立的处理器上时,保持它们本该执行的顺序。假共享是指多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效,当出现这个内存顺序冲突时,CPU必须清空流水线。这些其实在多核编程的环境下的locked-base的实现中也有类似的问题所以这里就不多展开。
4.结论和建议
无锁编程方式相对基于锁的编程方式,具备一定的优点,比如不会发生死锁,不会有优先级倒置,进行CAS操作的消耗比加锁操作轻很多等等。单从这个角度上讲在对应用程序不太复杂,而对操作实时性要求较高时,采用无锁多线程能发挥一定优势。在性能上基于CAS实现的硬件级的互斥,其单次操作性能比相同条件下的应用层的较为高效,但当多个线程并发时,硬件级的互斥引入的消耗一样很高(类似spin_lock)。 无锁算法及相关数据结构并不意味在所有的环境下都能带来整体性能的极大提升。循环CAS操作对时会大量占用cpu,对系统时间的开销也是很大。这也是基于循环CAS实现的各种自旋锁不适合做操作和等待时间太长的并发操作的原因。而通过对有锁程序进行合理的设计和优化,在很多的场景下更容易使程序实现高度的并发性。在开发维护的成本和复杂度上,无锁编程难度非常高,类似ABA的问题也比较难直观的探测和解决,并且实现细节和硬件平台相关性很强。目前理论和实践上只有少数数据结构可以支持实现无锁编程,比如队列、栈、链表、词典等,目前要在产品业务开发环境中进行大规模的无锁编程较为困难,更多的是用在部分特殊场景解决锁竞争等问题比较合适,比如操作系统中实现metux,semaphare, 一些语言的库的实现(比如 java current lib, lmax disprute)。若想在应用开发中尝试的Lock-Free的方案,建议可以选择合适的第三方lib实现。
附一. 些常见的相关lib库工具
https://github.com/mthssdrbrg/LockFreeStack
http://www.lmax.com/disruptor
http://www.cse.chalmers.se/research/group/noble/
http://www.cl.cam.ac.uk/research/srg/netos/lock-free/
http://www.rossbencina.com/code/lockfree
http://www.liblfds.org/
http://concurrencykit.org/
http://www.cl.cam.ac.uk/research/srg/netos/lock-free/

浙公网安备 33010602011771号