Michael-Scott非阻塞队列(lock-free)算法的C实现
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实现的更多相关文章
- 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理
· 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...
- 9.并发包非阻塞队列ConcurrentLinkedQueue
jdk1.7.0_79 队列是一种非常常用的数据结构,一进一出,先进先出. 在Java并发包中提供了两种类型的队列,非阻塞队列与阻塞队列,当然它们都是线程安全的,无需担心在多线程并发环境所带来的不可 ...
- java阻塞队列与非阻塞队列
在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. //使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入 ...
- 生产者-消费者 用非阻塞队列、Object.wait()、Object.notify()实现
非阻塞队列,需要考虑到: 1.并发中的同步 2.线程间通信 public class Quene_Pro_Con { //定义队列大小 private static int size = 10; // ...
- (原创)JAVA阻塞队列LinkedBlockingQueue 以及非阻塞队列ConcurrentLinkedQueue 的区别
阻塞队列:线程安全 按 FIFO(先进先出)排序元素.队列的头部 是在队列中时间最长的元素.队列的尾部 是在队列中时间最短的元素.新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素.链接 ...
- 生产者消费者模式--阻塞队列--LOCK,Condition--线程池
1.阻塞队列:http://www.cnblogs.com/dolphin0520/p/3932906.html 2.Condition 生产者消费者实现 :http://www.cnblogs.co ...
- Java并发容器之非阻塞队列ConcurrentLinkedQueue
参考资料:http://blog.csdn.net/chenchaofuck1/article/details/51660521 实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,阻塞队列就是 ...
- 多线程高并发编程(11) -- 非阻塞队列ConcurrentLinkedQueue源码分析
一.背景 要实现对队列的安全访问,有两种方式:阻塞算法和非阻塞算法.阻塞算法的实现是使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出队和入队各一把锁LinkedBloc ...
- 聊聊 JDK 非阻塞队列源码(CAS实现)
正如上篇文章聊聊 JDK 阻塞队列源码(ReentrantLock实现)所说,队列在我们现实生活中队列随处可见,最经典的就是去银行办理业务,超市买东西排队等.今天楼主要讲的就是JDK中安全队列的另一种 ...
随机推荐
- 什么是"抓包"?怎样"抓包"?
你是网络管理员吗?你是不是有过这样的经历:在某一天的早上你突然发现网络性能急剧下降,网络服务不能正常提供,服务器访问速度极慢甚至不能访问,网络交换机端口指示灯疯狂地闪烁.网络出口处的路由器已经处于满负 ...
- java基础---->java输入输出流
今天我们总结一下java中关于输入流和输出流的知识,博客的代码选自Thinking in java一书.我突然很想忘了你,就像从未遇见你. java中的输入流 huhx.txt文件的内容如下: I l ...
- split()有个坑
刚才在做DBMS课程设计的时候遇到了一个以前遇到过的问题不过这次我没有一眼认出来,想了好一会才想起来. 就是在用split()方法来分割路径名字符串的时候,比如 String path = “E:\s ...
- 从TCP三次握手说起–浅析TCP协议中的疑难杂症(2)
版权声明:本文由黄日成原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/108 来源:腾云阁 https://www.qclo ...
- 微软雅黑的Unicode码和英文名
中文名 英文名 Unicode编码 微软雅黑 Microsoft YaHei \5FAE\8F6F\96C5\9ED1 宋 体 SimSun \5B8B\4F53 黑 体 SimHei \9ED1\4 ...
- 【BZOJ1901】Zju2112 Dynamic Rankings 主席树+树状数组
[BZOJ1901]Zju2112 Dynamic Rankings Description 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j ...
- 爬虫实战【3】Python-如何将html转化为pdf(PdfKit)
前言 前面我们对博客园的文章进行了爬取,结果比较令人满意,可以一下子下载某个博主的所有文章了.但是,我们获取的只有文章中的文本内容,并且是没有排版的,看起来也比较费劲... 咋么办的?一个比较好的方法 ...
- [NGINX] - 配置文件优化 - NGINX.CONF
Nginx 本文主要针对公司的Nginx负载均衡配置进行解释,配置文件在最下方.因为公司没有使用PHP,所以NGINX里面并没有太多facgi模块相关优化 NGINX.CONF user 语 ...
- You don't have permission to access
局域网内配置Discuz,web端访问server端,出现以下反馈: Forbidden You don't have permission to access / on this server. 网 ...
- curl 模拟GET\POST请求,以及curl post上传文件
https://blog.csdn.net/fungleo/article/details/80703365