CAS原理及应用

CAS 无锁实现

为了实现多资源争用就引入了锁,但是锁的性能并不是那么好。一方面通过调整锁的粒度来优化,一方面提出了无锁的方式 – CAS。——Compare & Set,或是 Compare & Swap。

必须要明确的是这需要CPU指令的支持。在正常逻辑视角来看,对比,交换。是两个步骤,但是通过CPU指令集的优化可以把CAS作为原子操作。

代码

看一看内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。

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;
}

这个操作可以变种为返回bool值的形式(返回 bool值的好处在于,可以调用者知道有没有更新成功):

bool compare_and_swap (int *accum, int *dest, int newval)
{
  if ( *accum == *dest ) {
      *dest = newval;
      return true;
  }
  return false;
}

与CAS相似的还有下面的原子操作:(这些东西大家自己看Wikipedia吧)

  • Fetch And Add,一般用来对变量做 +1 的原子操作
  • Test-and-set,写值到某个内存位置并传回其旧值。汇编指令BST
  • Test and Test-and-set,用来低低Test-and-Set的资源争夺情况

进阶一 无锁队列

队列常常是使用在池技术上,比如进程池,线程池,连接池等等。在多并发操作上获取资源就需要对队列加锁,这边就是以CAS去实现资源争用。

入队

进队列用CAS实现的方式:

入队分为三个步骤。

  • 创建新节点。
  • 新节点加入到原有尾节点后。
  • 尾节点指向新节点。
    在这里插入图片描述

在这里,当多个线程(实例)向队列追加时,新节点加入到尾节点这个操作错在争用,需要使用CAS。代码如下。

EnQueue(x) //进队列
{
    //准备新加入的结点数据
    q = new record();
    q->value = x;
    q->next = NULL;
 
    do {
        p = tail; //取链表尾指针的快照
    } while( CAS(p->next, NULL, q) != TRUE); //如果没有把结点链在尾指针上,再试
 // 线程在这里异常退出
    CAS(tail, p, q); //置尾结点
}

以上代码还有一个风险:
如果T1 线程在使用CAS更新tail 指针之前gg了。这回导致其他线程检测 CAS(p->next, NULL, q) 永远为true。进入死循环。

优化代码:

EnQueue(x) //进队列改良版
{
    q = new record();
    q->value = x;
    q->next = NULL;
 
    p = tail;
    oldp = p
    do {
        while (p->next != NULL)
            p = p->next;
    } while( CAS(p.next, NULL, q) != TRUE); //如果没有把结点链在尾上,再试
 
    CAS(tail, oldp, q); //置尾结点
}

###出队
出队代码相对简单:

DeQueue() //出队列
{
    do{
        p = head;
        if (p->next == NULL){
            return ERR_EMPTY_QUEUE;
        }
    while( CAS(head, p, p->next) != TRUE );
    return p->next->value;
}

进程 ABA 问题

简答的说就是 A读取了某个状态后被B抢占了,然后B进行了一系列骚操作之后值并没有改变。A毫无感知,继续执行了。

若果CAS 保存的是指针在这里就会出问题。

你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。

解决ABA的问题

维基百科上给了一个解——使用double-CAS(双保险的CAS),例如,在32位系统上,我们要检查64位的内容

1)一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器。

2)只有这两个都一样,才算通过检查,要吧赋新的值。并把计数器累加1。

这样一来,ABA发生时,虽然值一样,但是计数器就不一样(但是在32位的系统上,这个计数器会溢出回来又从1开始的,这还是会有ABA的问题)

代码:

SafeRead(q)
{
    loop:
        p = q->next;
        if (p == NULL){
            return p;
        }
 
        Fetch&Add(p->refcnt, 1);
 
        if (p == q->next){
            return p;
        }else{
            Release(p);
        }
    goto loop;
}

无锁队列实现 陈皓

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值