无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
实现非阻塞同步的方案称为“无锁编程算法”( Non-blocking algorithm)。
lock-free是目前最常见的无锁编程的实现级别(一共三种级别):
使用lock实现线程同步有很多缺点:
* 产生竞争时,线程被阻塞等待,无法做到线程实时响应。
* dead lock。
* live lock。
* 优先级翻转。
* 使用不当,造成性能下降。
是最理想的模式,整个操作保证每个线程在有限步骤下完成。
保证系统级吞吐(system-wide throughput)以及无线程饥饿。
截止2011年,没有多少具体的实现。即使实现了,也需要依赖于具体CPU。
允许个别线程饥饿,但保证系统级吞吐。
确保至少有一个线程能够继续执行。
wait-free的算法必定也是lock-free的。
在任何时间点,一个线程被隔离为一个事务进行执行(其他线程suspended),并且在有限步骤内完成。
在执行过程中,一旦发现数据被修改(采用时间戳、版本号),则回滚。
也叫做乐观锁,即乐观并发控制(OOC)。
事务的过程是:1读取,并写时间戳;2准备写入,版本校验;3校验通过则写入,校验不通过,则回滚。
CAS( compare and swap) 原子操作,用来实现多线程下的变量同步。
保证了如果需要更新的地址没有被其他进程(线程)改动过,那么它可以安全的写入。
而这也是我们对于某个数据或者数据结构加锁要保护的内容,保证读写的一致性,不出现dirty data。
CAS原语有三个参数:
算法逻辑:
代码语言:javascript
复制
int compare_and_swap (int* reg, int oldval, int newval) {
ATOMIC();
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
return old_reg_val;
}
可在循环中不断执行CAS,如果共享变量没有改变,那么swap,在当前环境中写入,否则继续do-while的Retry-Loop。
ABA问题最容易发生在lock free算法中的,地址被重用的情况。
无锁相当于“锁”的粒度变小了,主要是“锁”HEAD和TAIL这两个关键资源。而不是整个数据结构。
thread1意图对val=1进行操作变成2,cas(*val,1,2)。
thread1先读取val=1;thread1被抢占(preempted),让thread2运行。
thread2 修改val=3,又修改回1。
thread1继续执行,发现期望值与“原值”(其实被修改过了)相同,完成CAS操作。
使用CAS会造成ABA问题,特别是在使用指针操作一些并发数据结构时。
解决方案
ABAʹ:添加额外的标记用来指示是否被修改。
代码语言:javascript
复制
# Java demo
AtomicInteger atom = new AtomicInteger(1);
boolean r = atom.compareAndSet(1, 2);
# C# demo
int i=1;
Interlocked.Increment(ref i);