Compare And Swap(CAS)实现无锁多生产者

1、CAS 原理

compare and swap,解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

当同时存在读写线程时,默认情况下是不保证线程安全的,因而需要利用信号量来进行线程同步(Synchronization),如关键代码段、互斥体等, 同时操作系统也提供了相应的API。然而同步并不总是满足条件的且有效率的,比如陷入内核时会有性能损失、死锁、活锁以及资源浪费等。

于是Lock-Free和Wait-Free的思想出现了,由于此时不存在读写线程的同步,因而在写线程运行时,读线程也在运行(多核中两个线程在不同的核上被调度运行),而且代码量减少,程序运行更快。而这一思想是通过CAS机制来实现,如下

template<typename T>
bool CAS(T* ptr, T expected, T fresh)
{
    if(*ptr != expected)
         return false;
    *ptr = fresh;
    return true;
}

CAS的原理是,将旧值与一个期望值进行比较,如果相等,则更新旧值,类型T = {char, short, int, __int64, …}等,以及指针(pointer to any type)。

注意CAS这里只是说明了原理,并不是真实的源代码实现,具体实现请参考操作系统。

在Windows API中,提供了很多原子操作(Atomic Operatoration),如InterlockedCompareExchange等一系列InterLocked函数,从汇编的角度来 讲,intel的XCHG指令即可以一个时钟周期内完成数据的交换(寄存器和内存的数据交换),使用方法可参考 InterlockedCompareExchange的反汇编代码。

考虑这样一种情况:存在多个读线程和一个写线程,在使用同步方法时, 很可能写线程并不能立即获得锁,最坏的情况下是写线程永远得不到锁,即进入活锁状态。但是使用CAS的方法时,便可以让读写线程并行运行,当写线程一旦更 新为新的共享数据时,读线程便能即时读出更新后的数据。

class Widget
{
    Data* p_;
    ...
    void Use() { ... use p_ ... }
    void Update() {
    Data * pOld, * pNew = new Data;
    do
    {
    pOld = p_;
    ...
    }while (!CAS(&p_, pOld, pNew));
    }
};

但随之而来会有一个疑问,Update函数中该何时删除旧数据呢,由于很有可能有别的读线程在使用旧数据。对于JAVA等有自动内存回收(GC)机制的语言环境而言,这不是问题,但对于C/C++等无GC机制的环境而言,旧数据的回收就比较棘手的问题了。

当然也存在很多的解决方法,这也成为CAS机制中最有趣最受讨论的问题,而且在不同条件下方法也不同。

2、实现无锁多生产者

struct node{
struct node *next;
int data;
}

struct node *queue;//队列头

多个消费者(多线程)都需要向这个queue插入数据

为了说明问题的复杂性,先看看只有一个消费者时的情况,插入队列的操作非常简单:

Step1) new_head->next = queue->head;

Step2) queue->head = new_head;

加入了多线程,问题变得复杂,以step 2为例,多个线程可能会同时进行这个操作,因此结果是不可预知的。

解决办法1)任何线程在进行step1之前先获取锁,得到step 2完成后再释放锁,这种办法是最简单的,但锁的性能开销较大,还可以考虑改进的办法。

一个比较妙的思路是:

每次操作之前先确认别的生产者没有在改变队列的头部,如果没有别的生产者正在操作,当前生产者就可以操作了。

do{
  old_head = queue->head;
  new_head->next = old_head;
  if (old_head == queue->head){
    queue->head = new_head;
  }
}while(queue->head != new_head)

意循环终止条件:

当queue->head等于new_head时,说明本生产者已经成功操作了队列

否则,说明本轮有其他生产者操作了队列,下轮再做尝试,直到成功为止

这样看起来可以保证只有一个生产者来操作队列了(其他的生产者),现在的问题是第4行和第5行无法保证原子执行,也就是说存在多个线程的4条件都成立,紧接着又都执行了,这样还是会出现错误。

这个问题如何解决呢?4和5步如果能原子性的执行,问题就很大程度上得到了解决。幸运的是不同架构的cpu都提供了类似cas/cmpxchg的指令,保证操作的原子性

下面的c代码说明了cas的含义(这里的代码是示意性的,实际的指令是原子性的)

int compare_and_swap (int* reg, int oldval, int newval)
{
  int old_reg_val = *reg;
  if (old_reg_val == oldval)
     *reg = newval;
  return old_reg_val;
}

有了这个指令之后,上面的代码就可以改写成多生产者安全的

do{
old_head = queue->head;
new_head->next = old_head;
val=cmpxchg(&queue->head, old_head, new_head);
}while(val!=old_head)

注意循环终止的判断条件:

当val == old_head时,说明3,4步之间没有生产者更改过队列头,操作已经成功

当val != old_head时,说明已经3,4步之间已经有其他生产者操作过队列,此时当前的生产者需要重新尝试操作队列

为何将2,3步放到循环内部呢?为了说明这个,可以假设将2,3放到循环外面会如何?假设第一轮其他生产者操作了队列,我们需要重新来过,重新来过时,queue->head已经是其他线程更新过的了,如果放到循环外面,old_head无法更新,而val则会返回新的head,此时判断条件会永远失败,导致死循环。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-02 23:03:47

Compare And Swap(CAS)实现无锁多生产者的相关文章

使用CAS实现无锁列队-链表

#include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <iostream> #include <sys/time.h> #include <pthread.h> using namespace std; #define MAXLEN 200000 #define NUM_THREADS 8 #define CAS __sync_bool_compare

CAS原子操作实现无锁及性能分析

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 13th, 2014 最近在研究nginx的自旋锁的时候,又见到了GCC CAS原子操作,于是决定动手分析下CAS实现的无锁到底性能如何,网上关于CAS实现无锁的文章很多,但少有研究这种无锁的性能提升的文章,这里就以实验结果和我自己的理解逐步展开. 1.什么是CAS原子操作 在研究无锁之前,我们需要首先了解一下CAS原子操作-

锁、CAS操作和无锁队列的实现

https://blog.csdn.net/yishizuofei/article/details/78353722 锁的机制 锁和人很像,有的人乐观,总会想到好的一方面,所以只要越努力,就会越幸运:有的人悲观,总会想到不好的一方面,患得患失,所以经常会做不好事.我一直把前一个当作为我前进的动力和方向,快乐充实的过好每一天. 常用的锁机制也有两种: 1.乐观锁:假设不会发生并发冲突,每次不加锁而去完成某项操作,只在提交操作时,检查是否违反数据完整性.如果因为冲突失败就继续重试,直到成功为止.而乐

Concurrent初探 --- Atomic 无锁

一.CAS算法 Compare And Swap,CAS算法的过程是这样:它包含3个参数CAS(V,E,N).V表示要更新的变量,E表示预期值,N表示新值.仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做.最后,CAS返回当前V的真实值.CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成操作.当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败.失败的线程不会被挂起,仅是被告知失败,并且允许再次尝

非阻塞同步算法与CAS(Compare and Swap)无锁算法

CAS无锁算法 要实现无锁(lock-free)的非阻塞算法有多种实现方法,其中CAS(比较与交换,Compare and swap)是一种有名的无锁算法.CAS, CPU指令,在大多数处理器架构,包括IA32.Space中采用的都是CAS指令,CAS的语义是"我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少",CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起

无锁-CAS原子操作

CAS原子操作--Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令. 大家应该还记得操作系统里面关于"原子操作"的概念,一个操作是原子的(atomic),如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构.原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分.有了这个原子操作这个保证我们就可以实现无锁了. 相对

转载:无锁队列的实现(CAS同步)

转自:http://coolshell.cn/articles/8239.html 关于无锁队列的实现,网上有很多文章,虽然本文可能和那些文章有所重复,但是我还是想以我自己的方式把这些文章中的重要的知识点串起来和大家讲一讲这个技术.下面开始正文. 关于CAS等原子操作 在开始说无锁队列之前,我们需要知道一个很重要的技术就是CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令.

无锁算法CAS 概述

无锁算法CAS 概述 JDK5.0以后的版本都引入了高级并发特性,大多数的特性在java.util.concurrent包中,是专门用于多线并发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发应用程序.主要包含原子量.并发集合.同步器.可重入锁,并对线程池的构造提供了强力的支持. 原子量是定义了支持对单一变量执行原子操作的类.所有类都有get和set方法,工作方法和对volatile变量的读取和写入一样.并发集合是原有集合框架的补充,为多线程并发程序提供了支持.主要有:Block

CAS(Compare And Swap)分析

CAS(Compare And Swap)指的是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令.这个指令会对内存中的共享数据做原子的读写操作. 简单介绍一下这个指令的操作过程:首先,CPU会将内存中将要被更改的数据与期望的值做比较.当这两个值相等时,CPU才会将内存中的数值替换为新的值,否则便不做操作.最后,CPU 会将当前变量的真实值返回.这一系列的操作是原子的. CAS是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它.而synchronized是一种悲观锁,它认