您的位置:首页 > 其它

CAS操作实现并发的优势、以及实现一个无锁队列、怎样解决ABA 问题

2017-10-31 23:20 796 查看
一、CAS(Compare and set)简介

1.概念:直译:比较和替换。简单来说–>使用一个期望值来和当前变量的值进行比较,如果当前的变量值与我们期望的值相等,就用一个新的值来更新当前变量的值

2.特点:CAS是一种系统原语也就是所谓的原子操作,由若干条指令完成,用于完成一个功能的过程,原语的执行过程必须是连续的,不允许被中断。CAS有三个操作数,V:旧的内存值,A:预期的值,B:要更新后的值。当A=V时,将V的值更新为B,否则什么都不做。

3.用处:设计并发操作时的一种技术。

二、利用CAS实现多线程并发的优势(以实现内置类型的自增自减操作的并发为例)

1.首先来谈谈何为并发,为什么要进行线程间的并发。

自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:

  假如某个时刻变量inc的值为10,线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

  那么两个线程分别进行了一次自增操作后,inc只增加了1

  我们期望的是在两个线程中都对inc进行了加一操作,然而最终得到的结果却是1。所以我们要对自增自减的这部分操作进行处理,使得他们的操作具有原子性,那么我们的程序就可以的到预期的效果了。

  

  我们可以想到很多种操作来实现线程间的并发。例如:锁、信号量等。锁也就是概念上的悲观锁:一个线程持有锁,会导致其他所有需要该锁的线程挂起,等待该线程释放锁。

另一种有效的解决办法就是乐观锁,每次操作都不加锁,而是假设没有冲突进而去完成某种操作,如果因为冲突失败就重试,乐观锁就是利用CAS来实现的。

但是,锁机制(悲观锁)会带来一系列的问题

1.一个线程持有锁,会导致其他需要该锁的线程全部挂起,导致线程的执行效率不高

2.在多线程竞争资源的情况下,加锁、解锁会导致过多的上下文切换以及调度时延,引起性能问题

3.如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置引起性能风险。

而CAS就可以在一定程度上解决这些潜在的问题。

三、CAS带来的一些问题及解决方法

1.ABA问题:CAS在要更新值的时候需要检查旧值是否有变化,如果没有发生变化则更新值。但如果一个值A变成了B又变成了A,那么CAS在检查数据时发现没有改变,但实际上已经改变了。更新了原有的值的话会造成许多不可预想的后果。接下来举例说明。

利用单链表实现栈



2.循环时间开销太大:如果CAS长时间执行不成功,则会给CPU带来交大的执行开销。处理器提供一种pause指令可以缓解这部分问题,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3。只能保证一个共享变量的原子操作。如果需要对多个共享变量进行同步,就得使用锁,或者将几个共享变量封装起来,使用CAS来进行同步。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

四、利用CAS怎样实现一个无锁队列(在此只表述以链表微为数据结构的实现)

在此单链表的数据结构是自己实现的

//入队操作、在此为了预防多线程一直死循环下去,在置尾节点之前,将p挪到最后的尾节点处
bool Endequeue(int x)
{
Node *p = new Ndoe(x);
p->next = NULL;
Node *q = tail;
Node *oldp = p;

do {
while (p->next != NULL)
p = p->next;
} while( CAS(q.next, NULL, p) != true); //如果没有把结点链在尾上,再试
CAS(tail, oldp, q); //置尾结点
return true;
}

//出队操作
bool Delqueue(int x)
{
do{
Node *p = head;
if (p->next == NULL){
return false;x`x`x`
}
while( CAS(head, p, p->next) != true);
return true;
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  CAS无锁队列ABA
相关文章推荐