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中安全队列的另一种 ...
随机推荐
- JS-简单地匀速运动框架
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- MyBatis——日志
Logging Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具: SLF4J Apache Commons Logging Log4j 2 Log4j JDK logging ...
- JSP自定义标签开发入门《转》
JSP自定义标签开发入门 一般情况下开发jsp自定义标签需要引用以下两个包 import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; ...
- navicat 激活流程
Navicat Premium 12激活 我自己测试了一下可以激活,很好用 原作链接:https://blog.csdn.net/loveer0/article/details/82016644 Na ...
- Cookie的介绍及使用
咱们不搞一开始就一大堆理论知识介绍,怕把人讲懵了...... 咱们换一个思维方式——"从现象看本质",先说说我们看到了什么,再从看到的现象中提出问题,最后深入寻找答案. 我们看到的 ...
- 查看Oracle的redo日志切换频率
1.Oracle log 每次切换会记录到告警日志中 设想写个方案来查看log切换频率来判断Oracle log是否应该更改大小. 2.sql a.查看redo日志信息 select * from v ...
- FW:stash install
先下载破解安装包.http://pan.baidu.com/s/1mgumBbE 我的安装环境. 说明下,经过我的测试. 如果系统内存低于 512M, 就不要折腾了,非常卡. 推荐 2048M 内存. ...
- Django自带的加密算法及加密模块
Django 内置的User类提供了用户密码的存储.验证.修改等功能,可以很方便你的给用户提供密码服务. 默认的Ddjango使用pbkdf2_sha256方式来存储和管理用的密码,当然是可以自定义的 ...
- js-jquery-SweetAlert2【三】INPUT TYPES
1.text swal({ title: 'Input something', input: 'text', showCancelButton: true, inputValidator: funct ...
- [.Net]System.OutOfMemoryException异常
1. 一个异常情景 加载15000条等高线,平均每条线有400个点到三维球上,等待时间太长.而且可能会报内存异常. 2. 不错的分析 http://wenku.baidu.com/view/14471 ...