CAS——Compare-and-swap

参考wiki

1.CAS的含义

  • CAS是用来实现多线程同步的原子指令。
    原子操作:比较内存值和给定值,在相等的情况下,将内存值修改为一个新值。

  • 伪代码

function cas(p : pointer to int, old : int, new : int) returns bool {
    if *p ≠ old {
        return false
    }
    *p ← new
    return true
}

2.基于CAS的算法

  • step1.读取内存的旧值,基于旧值计算新值
    step2.使用CAS替换内存为新值,前提是检测到内存依然等于旧值
    step3.如果检测失败,重新开始读去内存的旧值,基于旧值计算新值然后利用CAS写入新值。

  • atomic 加法示例
    即使在读取p的值 到 CAS之间,p的值被修改了,CAS会发现这个问题,并且进行重新取值、计算。

function add(p : pointer to int, a : int) returns int {
    done ← false
    while not done {
        value ← *p  // Even this operation doesn't need to be atomic.
        done ← cas(p, value, value + a)
    }
    return value + a
}

3.ABA问题

  • ABA问题发生在多线程同步的时候。线程P1前后两次读取内存位置,发现值一样就认为“nothing has changed”。事实上,在这两次读取的期间,线程P2可能修改了此内存,然后又将值修改回来了。
    线程P1继续执行的时候可能因为内存的隐藏修改而发生错误。

  • ABA问题示例
    1)栈开始时是top -> A -> B -> C
    2)线程1执行pop:
    ret = A;
    next = B;
    在执行CAS之前,线程被中断
    3)线程2开始执行pop:
    ret = A;
    next = B;
    CAS(A, B); //成功,top = B
    return A;
    4)线程2继续执行pop:
    ret = B;
    next = C;
    compare_exchange_weak(B, C); // 成功,top = C
    return B;
    5)delete B;
    6)线程将A压栈
    此时栈的内容是top -> A -> C
    7)线程1恢复运行
    CAS(A, B),此时将B设置为top栈顶,且B已经是释放的内存,会发生错误。

  /* Naive lock-free stack which suffers from ABA problem.*/
  class Stack {
    std::atomic top_ptr;
    //
    // Pops the top object and returns a pointer to it.
    //
    Obj* Pop() {
      while(1) {
        Obj* ret_ptr = top_ptr;
        if (!ret_ptr) return nullptr;
        // For simplicity, suppose that we can ensure that this dereference is safe
        // (i.e., that no other thread has popped the stack in the meantime).
        Obj* next_ptr = ret_ptr->next;
        // If the top node is still ret, then assume no one has changed the stack.
        // (That statement is not always true because of the ABA problem)
        // Atomically replace top with next.
        if (top_ptr.compare_exchange_weak(ret_ptr, next_ptr)) {
          return ret_ptr;
        }
        // The stack has changed, start over.
      }
    }
    //
    // Pushes the object specified by obj_ptr to stack.
    //
    void Push(Obj* obj_ptr) {
      while(1) {
        Obj* next_ptr = top_ptr;
        obj_ptr->next = next_ptr;
        // If the top node is still next, then assume no one has changed the stack.
        // (That statement is not always true because of the ABA problem)
        // Atomically replace top with obj.
        if (top_ptr.compare_exchange_weak(next_ptr, obj_ptr)) {
          return;
        }
        // The stack has changed, start over.
      }
    }
  };
  • 解决方法
    1)标记状态引用(Tagged state reference)
    使用额外的标记位,记录这个内存被成功修改的次数。
    2)使用中间节点(intermiediate nodes)
    不要使用数据成员而使用中间节点,这个怎么实现?
    3)推迟回收(deferred reclamation)
    这个怎么解决ABA问题?

4.LL/SC

  • load-link和store-conditional (LL/SC) 是用在多线程中实现同步的一对指令。
    LL返回内存的当前值,随后的SC会在同一个内存位置存入一个新值,前提是LL指令后对该内存位置没有更新。
    LL/SC实现lock-free atomic read-modify-write操作。
  • 任何更新发生,SC都会保证失败,即使LL读取的值重新被存回去。这会阻止ABA的问题的发生,因此LL/SC比CAS更强。
  • 两个操作之间的任何异常事件,比如上下文切换,另一个LL,甚至另一个load 或 store操作,会导致SC伪失败。这通常被称为weak LL/SC。

5. C++ compare_exchange_strong和compare_exchange_weak的区别

  • 在LL / SC芯片上,compare_exchange将以LL / SC的形式实现,因此可能发生虚假的失败。
    在虚假失败的情况下,compare_exchange_strong需要额外的开销来重试,因为strong版本在内存值等于expected值时,必须返回true,不允许虚假失败。
    1)库处理虚假失败
    在这种情况下,他们使用compare_exchange_strong。
    2)自己的代码中处理虚假失败
    在这种情况下他们使用compare_exchange_weak

你可能感兴趣的:(CAS——Compare-and-swap)