Michael-Scott非阻塞队列算法,即MS-queue算法,是1 9 9 6 年由Maged . M .Michael and M. L. Scott提出的,是最为经典的并发FIFO队列上的算法,目前很多对并发FIFO队列的研究都是基于这个算法来加以改进的。在共享内存的多核处理器上,这种基于Compare-and-swap(CAS)的算法在性能上要远远优于以前基于锁的算法,并且已经被Java并发包所采用。它的主要特点在于允许多线程并发的、无干扰的访问队列的头和尾。

MS-queue算法依赖于CAS原子操作,CAS操作是与处理器体系结构有关的,GCC中已经提供了内建的CAS相关的API,具体参见这里

bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...);
type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...);
/* 对应的伪代码 */
{ if (*ptr == oldval) { *ptr = newval; return true; } else { return false; } }
{ if (*ptr == oldval) { *ptr = newval; } return oldval; }
 

与CAS API一起的,还包括另一组自增、自减、与、或、非、异或原子操作的API。

type __sync_fetch_and_add(type *ptr, type value, ...); // m+n
type __sync_fetch_and_sub(type *ptr, type value, ...); // m-n
type __sync_fetch_and_or(type *ptr, type value, ...); // m|n
type __sync_fetch_and_and(type *ptr, type value, ...); // m&n
type __sync_fetch_and_xor(type *ptr, type value, ...); // m^n
type __sync_fetch_and_nand(type *ptr, type value, ...); // (~m)&n
/* 对应的伪代码 */
{ tmp = *ptr; *ptr op= value; return tmp; }
{ tmp = *ptr; *ptr = (~tmp) & value; return tmp; } // nand
 

使用这组API有很多好处,比如C/C++中自增自减及赋值操作都不是原子操作,如果是多线程程序需要使用全局计数器,程序就需要使用锁或者互斥量,对于较高并发的程序,会造成一定的性能瓶颈。而通过使用这组API,GCC通过在汇编级别的代码来保证赋值类操作的原子性,相对于涉及到操作系统系统调用和应用层同步的锁和互斥量,这组api的效率要高很多。

言归正传,回到MS-queue无锁(lock-free)队列上来。虽说MS-queue已经是大名鼎鼎了,不过找一个现成的C实现貌似还真不容易,C++的实现这里已经有了,是基于Boost的。另一个是复旦大学一个研究组的实现(这里),不过主要是针对64位机,CAS原语直接用汇编指令搞定的,觉得直接在32位下用或arm的GCC编译下会有问题。由于平时的项目开发用的基本是GCC编译器或arm的GCC,因此,自己实现了一个适用于32位机的、采用GCC内置CAS API的MS-queue。
ms_queue.h:

/*
** This file defines necessary data structures to implement a lock-free FIFO
** queue.
**
** Which is described in Michael and Scott's excellent paper appeared in PODC
** '96: "Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue
** Algorithms"
**
** Author: Jingcheng Li <jcli.china@gmail.com>
**
**/ #define __GNU_SOURCE
#include <stdlib.h>
#include <stdint.h> #define CAS __sync_bool_compare_and_swap typedef int data_type;
typedef struct queue_t queue_t;
typedef struct pointer_t pointer_t;
typedef struct node_t node_t; struct node_t; struct pointer_t {
node_t* ptr;
uint32_t count;
}; struct node_t {
data_type value;
pointer_t next;
}; struct queue_t {
pointer_t Head;
pointer_t Tail;
}; void initialize(queue_t *q)
{
node_t *node = NULL; node = malloc(sizeof(node_t));
node->next.ptr = NULL;
q->Head.ptr = q->Tail.ptr = node;
} void enqueue(queue_t* q, data_type value){
node_t *node = NULL;
pointer_t old_tail, tail, next, tmp; node = malloc(sizeof(node_t));
node->value = value;
node->next.ptr = NULL; while()
{
tail = q->Tail;
old_tail = tail;
next = tail.ptr->next;
/* tail may be changed in CAS after compare but before assign to q->Tail,
* so this is incorrect:
if (CAS((uint64_t*)&q->Tail, *(uint64_t*)&tail, *(uint64_t*)&old_tail))
this is correct:
if (CAS((uint64_t*)&q->Tail, *(uint64_t*)&tail, *(uint64_t*)&tail))
*/
if (CAS((uint64_t*)&q->Tail, *(const uint64_t*)&tail, *(const uint64_t*)&tail))
{
if (next.ptr == NULL)
{
tmp.ptr = node;
tmp.count = next.count+;
if (CAS((uint64_t*)&tail.ptr->next, *(const uint64_t*)&next, *(const uint64_t*)&tmp))
{
break;
}
}
else
{
tmp.ptr = next.ptr;
tmp.count = tail.count+;
CAS((uint64_t*)&q->Tail, *(const uint64_t*)&tail, *(const uint64_t*)&tmp);
}
}
} tmp.ptr = node;
tmp.count = tail.count+;
CAS((uint64_t*)&q->Tail, *(const uint64_t*)&tail, *(const uint64_t*)&tmp);
} int dequeue(queue_t *q, data_type* pvalue)
{
pointer_t old_head, head, tail, next, tmp;
while()
{
head = q->Head;
old_head = head;
tail = q->Tail;
next = head.ptr->next; /* head may be changed in CAS after compare but before assign to q->Head,
* so this is incorrect:
if (CAS((uint64_t*)&q->Head, *(uint64_t*)&head, *(uint64_t*)&old_head))
this is correct:
if (CAS((uint64_t*)&q->Head, *(uint64_t*)&head, *(uint64_t*)&head))
*/
if (CAS((uint64_t*)&q->Head, *(const uint64_t*)&head, *(const uint64_t*)&head))
{
if (head.ptr == tail.ptr)
{
if (next.ptr == NULL)
{
return ;
}
tmp.ptr = next.ptr;
tmp.count = tail.count+;
CAS((uint64_t*)&q->Tail, *(const uint64_t*)&tail, *(const uint64_t*)&tmp);
}
else
{
if (pvalue)
{
*pvalue = next.ptr->value;
}
tmp.ptr = next.ptr;
tmp.count = head.count+;
if (CAS((uint64_t*)&q->Head, *(const uint64_t*)&head, *(const uint64_t*)&tmp))
{
break;
}
}
}
} free(head.ptr);
return ;
}
 

test_queue.c:

 
#include <stdio.h>
#include <assert.h>
#include "ms_queue.h" pthread_t a_id[];
pthread_t b_id[]; queue_t queue;
void* put(void* a)
{
int i = , j;
int n = (int)a; for(j = n*; j<(n+)*; j++)
{
enqueue(&queue, j);
}
printf("put thread: %d exit\n", n);
} void* get(void* a)
{
int v;
int n = (int)a;
int cnt = ;
while(cnt--)
{
while( == dequeue(&queue, &v))
{
usleep();
}
}
printf("get thread: %d exit\n", n);
} int main()
{
int i, j;
initialize(&queue);
assert(NULL != queue.Head.ptr);
assert(NULL != queue.Tail.ptr);
for ( i = ; i < ; i++ )
{
pthread_create(&a_id[i], NULL, put, i);
pthread_create(&b_id[i], NULL, get, i);
} for ( i = ; i < ; i++ )
{
pthread_join(a_id[i], NULL);
pthread_join(b_id[i], NULL);
} assert( == dequeue(&queue, &j));
}

Michael-Scott非阻塞队列(lock-free)算法的C实现的更多相关文章

  1. 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

  2. 9.并发包非阻塞队列ConcurrentLinkedQueue

    jdk1.7.0_79  队列是一种非常常用的数据结构,一进一出,先进先出. 在Java并发包中提供了两种类型的队列,非阻塞队列与阻塞队列,当然它们都是线程安全的,无需担心在多线程并发环境所带来的不可 ...

  3. java阻塞队列与非阻塞队列

    在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. //使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入 ...

  4. 生产者-消费者 用非阻塞队列、Object.wait()、Object.notify()实现

    非阻塞队列,需要考虑到: 1.并发中的同步 2.线程间通信 public class Quene_Pro_Con { //定义队列大小 private static int size = 10; // ...

  5. (原创)JAVA阻塞队列LinkedBlockingQueue 以及非阻塞队列ConcurrentLinkedQueue 的区别

    阻塞队列:线程安全 按 FIFO(先进先出)排序元素.队列的头部 是在队列中时间最长的元素.队列的尾部 是在队列中时间最短的元素.新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素.链接 ...

  6. 生产者消费者模式--阻塞队列--LOCK,Condition--线程池

    1.阻塞队列:http://www.cnblogs.com/dolphin0520/p/3932906.html 2.Condition 生产者消费者实现 :http://www.cnblogs.co ...

  7. Java并发容器之非阻塞队列ConcurrentLinkedQueue

    参考资料:http://blog.csdn.net/chenchaofuck1/article/details/51660521 实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,阻塞队列就是 ...

  8. 多线程高并发编程(11) -- 非阻塞队列ConcurrentLinkedQueue源码分析

    一.背景 要实现对队列的安全访问,有两种方式:阻塞算法和非阻塞算法.阻塞算法的实现是使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出队和入队各一把锁LinkedBloc ...

  9. 聊聊 JDK 非阻塞队列源码(CAS实现)

    正如上篇文章聊聊 JDK 阻塞队列源码(ReentrantLock实现)所说,队列在我们现实生活中队列随处可见,最经典的就是去银行办理业务,超市买东西排队等.今天楼主要讲的就是JDK中安全队列的另一种 ...

随机推荐

  1. ajax 跨域访问 :Access-Control-Allow-Origin

    一说到ajax跨域.首先想到的就是jsonp  . JSONP方法是一种非官方方法,而且这种方法只支持GET方式,不如POST方式安全. 即使使用jQuery的jsonp方法,type设为POST,也 ...

  2. su命令cannot set groups: Operation not permitted的解决方法

    版权声明:本文由曾倩倩原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/103 来源:腾云阁 https://www.qclo ...

  3. cocos2dx3.1从零学习(二)菜单、场景切换、场景传值

    转:http://www.it165.net/pro/html/201406/16195.html 回顾一下上一篇的内容,我们已经学会了创建一个新的场景scene,添加sprite和label到层中, ...

  4. linux下安装F-prot杀毒软件

    一. f-prot的安装 1.首先我们要创建一个带有超级权限的用户 sudo passwa root 2.su 切换用户 3.下载F-prot http://www.f-prot.com/downlo ...

  5. Windows Phone 7 检查手机网络

    using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Wi ...

  6. Unity3D笔记三 物理引擎

    一.物理引擎 1.物理引擎就是模拟真实世界中物体碰撞.跌落等反应,通过Ballance.愤怒的小鸟来理解什么是物理引擎.Unity的物理引擎使用的是NviDIA的PhysX.2.选中一个游戏对象,主菜 ...

  7. jfinal关联查询给dto添加表结构以外的字段并返回的处理方式

    官网栗子: http://www.jfinal.com/doc/5-10 5.10 表关联操作 JFinal ActiveRecord 天然支持表关联操作,并不需要学习新的东西,此为无招胜有招.表关联 ...

  8. Spring AOP依赖包

    Spring4和2.5发生了很大的变化,原来的spring2.5很多倚赖的jar包都是随着spring一起发布的,现在spring4已 经不再发布倚赖包,需要你自己去导入 1.org.springfr ...

  9. 结合Ajax做地区内容切换!(城市切换)

    实现原理:点击,进行异步保存Cookie , 根据保存的Cookie来切换城市内容: 简单HTML示例: <!doctype html> <html> <head> ...

  10. 08.Curator缓存

        可以利用ZooKeeper在集群的各个节点之间缓存数据.每个节点都可以得到最新的缓存的数据.Curator提供了三种类型的缓存方式:Path Cache,Node Cache 和Tree Ca ...