无锁队列
来源:互联网 发布:淘宝怎么设置客服接待 编辑:程序博客网 时间:2024/06/09 12:28
在开始说无锁队列之前,我们需要知道一个很重要的技术就是CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令。有了这个原子操作,我们就可以用其来实现各种无锁(lock free)的数据结构。
这个操作用C语言来描述就是下面这个样子:(代码来自Wikipedia的Compare And Swap词条)意思就是说,看一看内存*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的资源争夺情况
3) C++11中的CAS
C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library)
template< class T >bool atomic_compare_exchange_weak( std::atomic* obj, T* expected, T desired );template< class T >bool atomic_compare_exchange_weak( volatile std::atomic* obj, T* expected, T desired );
我们先来看一下进队列用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); //置尾结点}我们可以看到,程序中的那个 do- while 的 Re-Try-Loop。就是说,很有可能我在准备在队列尾加入结点时,别的线程已经加成功了,于是tail指针就变了,于是我的CAS返回了false,于是程序再试,直到试成功为止。这个很像我们的抢电话热线的不停重播的情况。
你会看到,为什么我们的“置尾结点”的操作(第12行)不判断是否成功,因为:
- 如果有一个线程T1,它的while中的CAS如果成功的话,那么其它所有的 随后线程的CAS都会失败,然后就会再循环,
- 此时,如果T1 线程还没有更新tail指针,其它的线程继续失败,因为tail->next不是NULL了。
- 直到T1线程更新完tail指针,于是其它的线程中的某个线程就可以得到新的tail指针,继续往下走了。
这里有一个潜在的问题——如果T1线程在用CAS更新tail指针的之前,线程停掉或是挂掉了,那么其它线程就进入死循环了。下面是改良版的EnQueue()
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); //置尾结点}我们让每个线程,自己fetch 指针 p 到链表尾。但是这样的fetch会很影响性能。而通实际情况看下来,99.9%的情况不会有线程停转的情况,所以,更好的做法是,你可以接合上述的这两个版本,如果retry的次数超了一个值的话(比如说3次),那么,就自己fetch指针。
好了,我们解决了EnQueue,我们再来看看DeQueue的代码:(很简单,我就不解释了)
DeQueue() //出队列{ do{ p = head; if (p->next == NULL){ return ERR_EMPTY_QUEUE; } while( CAS(head, p, p->next) != TRUE ); return p->next->value;}我们可以看到,DeQueue的代码操作的是 head->next,而不是head本身。这样考虑是因为一个边界条件,我们需要一个dummy的头指针来解决链表中如果只有一个元素,head和tail都指向同一个结点的问题,这样EnQueue和DeQueue要互相排斥了。
注:上图的tail正处于更新之前的装态。
- 无锁环形队列
- 无锁队列
- 无锁队列实现
- 无锁队列
- 无锁队列
- 无锁队列
- 无锁队列
- 高效无锁队列
- [c++]无锁队列
- 无锁队列
- Boost无锁队列
- KFIFO无锁队列
- 无锁队列(一)
- MpscLinkedQueue 无锁队列
- 无锁队列入门
- 无锁队列
- 无锁队列的实现
- 无锁队列的实现
- 矩阵快速幂
- Java过滤器配置使用
- 写在2017年秋招之后的话
- 机械波简述------说说纵波(声波是一种纵波)
- android图片状态栏实现沉浸式状态栏
- 无锁队列
- 危桥僵尸和创新思维
- Hive的分桶详解
- 数据结构8————串的BF匹配模式和KMP匹配模式
- 关键字--exists用法
- [USACO 2013 Jan]Island Travels
- 单例详解
- 抽象工厂方法
- 开发日记 X