队列是我们非常常用的数据结构,用来提供数据的写入和读取功能,而且通常在不同线程之间作为数据通信的桥梁。不过在将无锁队列的算法之前,需要先了解一下CAS(compare and swap)的原理。由于多个线程同时操作同一个数据,其中肯定是存在竞争的,那么如何能够针对同一个数据进行操作,而且又不用加锁呢? 这个就需要从底层,CPU层面支持原子修改操作,比如在X86的计算机平台,提供了XCHG指令,能够原子的交互数值。

  从开发语言的层面,比如C++11中,就提供了atomic_compare_exchange_weak函数,来实现CAS。

  1. lockless queue,enqueue,dequeue操作的算法

  (1) enqueue算法:

  

  enqueue时,先将需要加入队尾的数据创建出来,然后在一个循环操作中,将数据加入队尾,如果加入失败,那么就更新当前的队尾指针,直到加入成功,然后循环结束。最后调整队尾指针。

  (2) dequeue算法

  

  dequeue时,在循环操作中,使用CAS将队列头指针,变成头指针的下一个指针,如果失败,持续操作直到成功。最后返回头指针指向的值。

  2. ABA问题,及解决办法

   从上面的算法中,可以看出采用CAS方式实现的无锁队列的算法过程,不过由于CAS操作本身的特殊性(通过判断当前被变换的值,是否发生过变化),可能会在某些情况下引起ABA问题。

  那么首先什么是ABA问题呢? wiki上有这样一个说明的例子:

  Natalie is waiting in her car at a red traffic light with her children. Her children start fighting with each other while waiting, and she leans back to scold them. Once their fighting stops, Natalie checks the light again and notices that it's still red. However, while she was focusing on her children, the light had changed to green, and then back again. Natalie doesn't think the light ever changed, but the people waiting behind her are very mad and honking their horns now.

  意思就是说,Natalie在等红灯的时候,由于回头管孩子,错过了绿灯,等她再回过头看信号灯的时候,又是红灯了。

  这其实就是一个ABA问题,虽然中间信号灯发生了变化,但是Natalie却不知道。

  

  用C++中的一个stack来说明,stack代码如下:

/* Naive lock-free stack which suffers from ABA problem.*/
class Stack {
std::atomic<Obj*> top_ptr;
//
// Pops the top object and returns a pointer to it.
//
Obj* Pop() {
while() {
Obj* ret_ptr = top_ptr;
if (!ret_ptr) return std::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() {
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.
}
}
};

  假设,stack初始化为top → A → B → C

  线程1先执行 

ret = A;
next = B;

  然后在线程1执行compare_exchange_weak之前被中断,换成线程2执行。

{ // 线程2先pop出A
ret = A;
next = B;
compare_exchange_weak(A, B) // Success, top = B
return A;
} // Now the stack is top → B → C
{ // 线程2再pop出B
ret = B;
next = C;
compare_exchange_weak(B, C) // Success, top = C
return B;
} // Now the stack is top → C
delete B;
{ // 最后线程2将A再push进stack
A->next = C;
compare_exchange_weak(C, A) // Success, top = A
}

  当线程2执行完所有这些操作之后,换成线程1执行,线程1的compare_exchange_weak是会成功执行的,因为它不知道top_ptr已经被修改过了。

  通常针对ABA问题的解决办法,就是针对操作的数据加上一个原子操作的使用计数,在CAS执行之前,先获取一下计数是否和之前一样,如果不一样,就说明数据已经被修改过了。

无锁队列以及ABA问题的更多相关文章

  1. 一个可无限伸缩且无ABA问题的无锁队列

    关于无锁队列,详细的介绍请参考陈硕先生的<无锁队列的实现>一文.然进一步,如何实现一个不限node数目即能够无限伸缩的无锁队列,即是本文的要旨. 无锁队列有两种实现形式,分别是数组与链表. ...

  2. boost 无锁队列

    一哥们翻译的boost的无锁队列的官方文档 原文地址:http://blog.csdn.net/great3779/article/details/8765103 Boost_1_53_0终于迎来了久 ...

  3. CAS简介和无锁队列的实现

    Q:CAS的实现 A:gcc提供了两个函数 bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)// ...

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

    https://blog.csdn.net/yishizuofei/article/details/78353722 锁的机制 锁和人很像,有的人乐观,总会想到好的一方面,所以只要越努力,就会越幸运: ...

  5. HashMap的原理与实 无锁队列的实现Java HashMap的死循环 red black tree

    http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html https://zh.wikipedia.org/wiki/%E7%BA ...

  6. zeromq源码分析笔记之无锁队列ypipe_t(3)

    在上一篇中说到了mailbox_t的底层实际上使用了管道ypipe_t来存储命令.而ypipe_t实质上是一个无锁队列,其底层使用了yqueue_t队列,ypipe_t是对yueue_t的再包装,所以 ...

  7. 无锁队列--基于linuxkfifo实现

    一直想写一个无锁队列,为了提高项目的背景效率. 有机会看到linux核心kfifo.h 原则. 所以这个实现自己仿照,眼下linux我们应该能够提供外部接口. #ifndef _NO_LOCK_QUE ...

  8. Go语言无锁队列组件的实现 (chan/interface/select)

    1. 背景 go代码中要实现异步很简单,go funcName(). 但是进程需要控制协程数量在合理范围内,对应大批量任务可以使用"协程池 + 无锁队列"实现. 2. golang ...

  9. 基于无锁队列和c++11的高性能线程池

    基于无锁队列和c++11的高性能线程池线程使用c++11库和线程池之间的消息通讯使用一个简单的无锁消息队列适用于linux平台,gcc 4.6以上   标签: <无>   代码片段(6)[ ...

随机推荐

  1. 运用C#生成docx格式的报表

    这几天在北京做一个大桥的监测系统的项目,涉及到一个功能,那就是采集数据,处理后,希望能自动生成一个报表,只需要在一个模板的基础上加几个数就可以了,但因为希望生成的是.docx格式的word2007/2 ...

  2. Redis哈希表的实现要点

    Redis哈希表的实现要点 哈希算法的选择 针对不同的key使用不同的hash算法,如对整型.字符串以及大小写敏感的字符串分别使用不同的hash算法: 整型的Hash算法使用的是Thomas Wang ...

  3. monkey中的一些问题

    一起来看下导致App Crash的那些原因: 1.     空指针异常:错误日志定位java.lang.NullPointerException,详细日志记录如下 2.     安全异常:错误日志定位 ...

  4. 《BI那点儿事》数据流转换——导入列、导出列

    导入列: 导入列例子现在来做一个例子:创建路径D:\Pictures随便在路径D:\Pictures中粘贴4个比较小的图像文件命名为01.png.02.png.03.png.04.png在路径D:\P ...

  5. memcache内存估算整理

    参考文章: http://blog.csdn.net/tonyxf121/article/details/7906428 http://zhihuzeye.com/archives/2361 memc ...

  6. HTML基础(2)

    常见标签 1.img标签 <img src="图片地址" alt="图片名"/>  图片(单标签) alt属性 是图片名字,是给百度搜索引擎抓取使用 ...

  7. iOS - Mac OS X 终端设置

    Mac OS X 终端设置 1)Finder 中显示资源库 方法一: 在 "终端" 中输入下面的命令: 显示: chflags nohidden ~/Library/ 隐藏: ch ...

  8. python的异常小结与assert

    异常的处理 一. 二. -----------------2016-4-20 18:04:06--

  9. 20160928_CentOS6.4x64_Oracle11gR2x64

    1.“Oracle Database 11g Release 2” 下载地址: http://www.oracle.com/technetwork/database/enterprise-editio ...

  10. react mixins编写

    var LogMixin = { componentWillMount: function () { console.log('Component will mount'); }, component ...