无锁队列以及ABA问题
队列是我们非常常用的数据结构,用来提供数据的写入和读取功能,而且通常在不同线程之间作为数据通信的桥梁。不过在将无锁队列的算法之前,需要先了解一下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问题的更多相关文章
- 一个可无限伸缩且无ABA问题的无锁队列
关于无锁队列,详细的介绍请参考陈硕先生的<无锁队列的实现>一文.然进一步,如何实现一个不限node数目即能够无限伸缩的无锁队列,即是本文的要旨. 无锁队列有两种实现形式,分别是数组与链表. ...
- boost 无锁队列
一哥们翻译的boost的无锁队列的官方文档 原文地址:http://blog.csdn.net/great3779/article/details/8765103 Boost_1_53_0终于迎来了久 ...
- CAS简介和无锁队列的实现
Q:CAS的实现 A:gcc提供了两个函数 bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)// ...
- 锁、CAS操作和无锁队列的实现
https://blog.csdn.net/yishizuofei/article/details/78353722 锁的机制 锁和人很像,有的人乐观,总会想到好的一方面,所以只要越努力,就会越幸运: ...
- HashMap的原理与实 无锁队列的实现Java HashMap的死循环 red black tree
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html https://zh.wikipedia.org/wiki/%E7%BA ...
- zeromq源码分析笔记之无锁队列ypipe_t(3)
在上一篇中说到了mailbox_t的底层实际上使用了管道ypipe_t来存储命令.而ypipe_t实质上是一个无锁队列,其底层使用了yqueue_t队列,ypipe_t是对yueue_t的再包装,所以 ...
- 无锁队列--基于linuxkfifo实现
一直想写一个无锁队列,为了提高项目的背景效率. 有机会看到linux核心kfifo.h 原则. 所以这个实现自己仿照,眼下linux我们应该能够提供外部接口. #ifndef _NO_LOCK_QUE ...
- Go语言无锁队列组件的实现 (chan/interface/select)
1. 背景 go代码中要实现异步很简单,go funcName(). 但是进程需要控制协程数量在合理范围内,对应大批量任务可以使用"协程池 + 无锁队列"实现. 2. golang ...
- 基于无锁队列和c++11的高性能线程池
基于无锁队列和c++11的高性能线程池线程使用c++11库和线程池之间的消息通讯使用一个简单的无锁消息队列适用于linux平台,gcc 4.6以上 标签: <无> 代码片段(6)[ ...
随机推荐
- rmi远程调用
1.在服务器端程序中的spring-servlet.xml中添加 <bean id="userService" class="org.springframework ...
- ShareSDK第三方登录代码
- (IBAction)YYSJBut:(UIButton *)sender{ if (sender.tag == 7) { [self AuthLogin:SSDKPlat ...
- linux权限,所有者、所在组、其他组(其他人员),chmod,chown
用户组 在linux中的每个用户必须属于一个组,不能独立于组外.在linux中每个文件有所有者.所在组.其它组的概念 - 所有者 - 所在组 - 其它组 - 改变用户所在的组 所有者 一般为文件的创建 ...
- Hibernate <一级缓存>
Hibernate缓存分为三级: 一级缓存:基于事务级别(内存)的缓存,也可以成为session级别缓存 二级缓存:依赖于第三方,当请求一个对象时,先在缓存里面查找,如果没有就执行查询语句 查询缓存: ...
- timer控件、三级联动
timer控件: 实现时间日期自增长: using System; using System.Collections.Generic; using System.ComponentModel; usi ...
- jQuery学习笔记整理
一.子元素选择器.:nth-child:匹配父元素下的第N个子或者奇偶元素.注意:序号是从1开始的,而eq是从0开始计数的!它匹配的是前方选择器选择到的元素的父元素下面的第几个元素.例如:ul li: ...
- MFC编程入门之十五(对话框:一般属性页对话框的创建及显示)
属性页对话框包括向导对话框和一般属性页对话框两类,上一节讲了如何创建并显示向导对话框,本节将继续介绍一般属性页对话框的创建和显示. 实际上,一般属性页对话框的创建和显示过程和向导对话框是很类似的.将上 ...
- centos 安装redis自启动要点
1.redis.conf a.daemonize yes b.pidfile /var/run/xxx.pid 2./etc/init.d/redis //加了下面三个注释部分,才支持设置开机自启动 ...
- 解决 Cannot find OpenSSL's <evp.h>
yum install openssl openssl-devel ln -s /usr/lib64/libssl.so /usr/lib/
- Laravel 设置时区
打开laravel框架目录下app/config/app.php 找到参数'timezone'='UTC',设置'timezone'='Asia/Shanghai',时间就正常了.