无锁编程技术及实现

1.基于锁的编程的缺点

  • 多线程编程是多CPU系统在中应用最广泛的一种编程方式,在传统的多线程编程中,多线程之间一般用各种锁的机制来保证正确的对共享资源(share resources)进行访问和操作。
  • 在多线程编程中只要需要共享某些数据,就应当将对它的访问串行化。比如像++count(count是整型变量)这样的简单操作也得加锁,因为即便是增量操作这样的操作,,实际上也是分三步进行的:读、改、写(回)。
movl x, %eax
addl $1, %eax
movl %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.无锁编程的定义

  • 无锁编程按字面最直观的理解是不使用锁的情况下实现多线程之间对变量同步和访问的一种程序设计实现方案。一个锁无关的程序能够确保它所有线程中至少有一个能够继续往下执行,而有些线程可能会被的延迟。然而在整体上,在某个时刻至少有一个线程能够执行下去。作为整体进程总是在前进的,尽管有些线程的进度可能没有其它线程进行的快。

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协议)。
  • 最后这里随便说一下CAS操作的ABA的问题,所谓的ABA的问题简要的说就是,线程a先读取了要对比的值v后,被线程b抢占了,线程b对v进行了修改后又改会v原来的值,线程1继续运行执行CAS操作的时候,无法判断出v的值被改过又改回来。
  • 解决ABA的问题的一种方法是,一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器;或者对CAS的数值加上版本号。 

你可能感兴趣的:(Linux系统编程)