多线程编程是多CPU系统在中应用最广泛的一种编程方式,在传统的多线程编程中,多线程之间一般用各种锁的机制来保证正确的对共享资源(share resources)进行访问和操作。这种基于锁机制的多线程同步存在以下问题:
1产生竞争时,线程被阻塞等待,无法做到线程实时响应;
2 死锁
3 活锁
4 申请和释放锁的操作,增加了很多访问共享资源的消耗,尤其是在锁竞争(lock-contention)很严重的时候;
5 优先级翻转
无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)是当前多线程编程的一大技术,当然也只是使用特定场景,并不能取代有锁编程。
非同步阻塞的实现可以分成三个级别:wait-free/lock-free/obstruction-free。
wait-free
是最理想的模式,整个操作保证每个线程在有限步骤下完成。
保证系统级吞吐(system-wide throughput)以及无线程饥饿。
截止2011年,没有多少具体的实现。即使实现了,也需要依赖于具体CPU。
lock-free
允许个别线程饥饿,但保证系统级吞吐。
确保至少有一个线程能够继续执行。
wait-free的算法必定也是lock-free的。
obstruction-free
在任何时间点,一个线程被隔离为一个事务进行执行(其他线程suspended),并且在有限步骤内完成。在执行过程中,一旦发现数据被修改(采用时间戳、版本号),则回滚。
也叫做乐观锁,即
乐观并发控制(OOC)。事务的过程是:1读取,并写时间戳;2准备写入,版本校验;3校验通过则写入,校验不通过,则回滚。lock-free必定是obstruction-free的。
主要实现(
核心是希望操作原子化,不希望被其他线程中途打扰)
引入了CAS(compare-and-swap)原语指令。CAS是一组原语指令,用来实现多线程下的变量同步。在 x86 下的指令CMPXCHG实现了CAS,前置LOCK既可以达到原子性操作。截止2013,大部分多核处理器均支持CAS。CAS原语有三个参数,内存地址,期望值,新值。如果内存地址的值==期望值,表示该值未修改,此时可以修改成新值。否则表示修改失败,返回false,由用户决定后续操作。
Bool CAS(T* addr, T expected, T newValue)
{
if( *addr == expected )
{
*addr = newValue;
return true;
}
else
return false;
}
主要问题:ABA问题:
thread1意图对val=1进行操作变成2,cas(*val,1,2)。
thread1先读取val=1;thread1被
抢占(preempted),让thread2运行。
thread2 修改val=3,又修改回1。
thread1继续执行,发现期望值与“原值”(其实被修改过了)相同,完成CAS操作。
使用CAS会造成ABA问题,特别是在使用指针操作一些并发数据结构时。
解决方案
ABAʹ:添加额外的标记tag用来指示是否被修改。