多核和多CPU编程——ABA的问题

一、无锁编程

在前面已经多次提到过无锁编程或者说非阻塞算法,无锁编程的原理就是compare-and-swap,一般通过在一个循环里比较值的变化来判断原有的内存值和现在的内存值是否相同,如果相同把新的值赋值给原有内存值(是指在多线程操作中),即原有内存值为old(预期值),新的数据值为new(希望值),现内存值为cur(一般为原子变量),比较old和cur,如果相等,则把new赋值old(old==new),否则啥都不干。
看下面的一个例子:

#include <atomic>
 
template<class T>
struct node
{
    T data;
    node* next;
    node(const T& data) : data(data), next(nullptr) {}
};
 
template<class T>
class stack
{
    std::atomic<node<T>*> head;
 public:
    void push(const T& data)
    {
        node<T>* new_node = new node<T>(data);
 
        // put the current value of head into new_node->next
        new_node->next = head.load(std::memory_order_relaxed);
 
        // now make new_node the new head, but if the head
        // is no longer what's stored in new_node->next
        // (some other thread must have inserted a node just now)
        // then put that new head into new_node->next and try again
        while(!std::atomic_compare_exchange_weak_explicit(
                                &head,
                                &new_node->next,
                                new_node,
                                std::memory_order_release,
                                std::memory_order_relaxed))
                ; // the body of the loop is empty
// note: the above loop is not thread-safe in at least
// GCC prior to 4.8.3 (bug 60272), clang prior to 2014-05-05 (bug 18899)
// MSVC prior to 2014-03-17 (bug 819819). See member function version for workaround
    }
};
 
int main()
{
    stack<int> s;
    s.push(1);
    s.push(2);
    s.push(3);
}

后面的内存序在X86上基本可以忽略,而c++中提供的weak和strong在X86上也可以忽略,如果是跨平台,推荐使用的是weak。

二、ABA

无锁编程对于操作非指针类型的原子操作没有什么问题,但在操作指针类型时,会出现ABA的问题。而根据实际产生的状况ABA分成了两大类情况:
1、无GC环境
举一个指针操作问题,假使当前队列中的指针为p0,其指向的内容为d0,设置为新值;假使线程切换,另外一线程将p0内容修改为d1,然后线程又切换回原有线程,CAS比较,发现p0==p0,则将d0重新赋值给p0,而将真正的修改数据丢弃。
2、GC环境
多线程操作一个队列,将A->B->C,换成E->B->C,当前线程读取A,切换线程,另外线程删除B,此时状态为A->C,然后回到原线程,CAS没问题,E替换A,最终状态E->C,不是期望。

三、解决方法

ABA现象这在链表和队列操作指针时,多线程删除节点(无锁编程),就极有可能出现这种情况。那么,出现这种情况有什么解决方法呢?
对于无GC环境:
1、最粗暴的方式就是不使用回收节点,即不重用内存节点。这个有垃圾回收机制的语言等于变相实现了这种方式。
2、自己实现一个小型垃圾回收器。专门处理这种CAS的指针操作。
3、增加中间节点标记法。标记哪些节点是被修改过。
4、RCU,即每次使用都拷贝创建一个新的节点来应用。
5、使用险象指针,此种指针的指向内容无法修改删除 。
对于有GC环境:
1、在比较时增加轻微修改,把ABA换成ABA`,这样就可以间接规避,这在某些机器上实现了。JAVA中增加了一个戳记的CAS也是类似。

四、总结

事物总是有其两面性,有好的一面,必然有不好的一面。那么最重的是在实际的应用场合中,发挥好的一面,抑制或者消除不好的一面。这就需要程序员搞懂内部的具体细节,有的放矢。片面的追求效率或者片面的追求安全,都是走向一条危险的路。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值