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. MyBatis——Java API

    Java API 既然你已经知道如何配置 MyBatis 和创建映射文件,你就已经准备好来提升技能了. MyBatis 的 Java API 就是你收获你所做的努力的地方.正如你即将看到的,和 JDB ...

  2. MongoDB复制集原理

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

  3. 谈谈KV存储集群的设计要点

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

  4. 详解struts2中配置action的方法

    如何解决action太多的问题??我们因为需要不同的方法,所以往往建立很多不同的类,但是每个类中往往仅仅几行代码,不仅浪费了时间,而且配置起来也很繁琐,所以,建立一个共有的类,然后根据以下方式来操作, ...

  5. vim命令删除后重新安装

    个人原创博客,转载请注明,否则追究法律责任 2017-09-30-09:51:20 1,删除vim命令.模拟错误 [root@localhost ~]# which vim/usr/bin/vim[r ...

  6. rest_framework之解析器详解 05

    解析器就是服务端写api,对于前端用户发来的数据进行解析.解析完之后拿到自己能用数据. 本质就是对请求体中的数据进行解析. django的解析器 post请求过来之后,django 的request. ...

  7. Groovy中的面向对象

    Groovy中的面向对象 前面说到groovy支持脚本和类,前面一节已将简单说了脚本和类之间的关系,这一节主要介绍一下groovy中类的相关知识,即面向对象相关知识. 1.类型 1.1 原始类型 gr ...

  8. MVC之路由

    1.路由规则理解: 在MVC项目中,路由是一个独立的模块,需要引入System.Web.Routing ,路由的作用就是确定控制器和行为,同时确定其他的参数,然后将这些信息传递个相应的控制器和行为. ...

  9. Only a type can be imported. classname resolves to a package的解决

    Only a type can be imported. l1.l2.MyClass resolves to a package ==========这里是解决方案=============== 把生 ...

  10. c# 自定义控件之小小进度条

    先看效果图: 非常简洁的一个进度条. 完整项目源码下载:http://files.cnblogs.com/files/tuzhiyuan/%E8%BF%9B%E5%BA%A6%E6%9D%A1%E6% ...