多个写线程一个读线程的无锁队列实现

之前的一篇博客中,写了一个在特殊情况下,也就是只有一个读线程和一个写线程的情况下,的无锁队列的实现。其中甚至都没有利用特殊的原子加减操作,只是普通的运算。这样做的原因是,即使是特殊的原子加减操作,也比普通的加减运算复杂度高很多。因此文中的实现方法可以达到很高的运行效率。

但是,有的情况下并不是只有一个读线程和一个写线程。越是一般化的实现,支持的情况越多,但是往往损失的性能也越多。作者看到过一个实现(http://www.oschina.net/code/snippet_732357_13465),可以实现一个读线程,多个写线程,或者相反,一个写线程,多个读线程。这篇文章中作者采用了原子加减的操作。所以这样的实现的运行效率会稍有点低。那么,如果情况稍特殊一点,比如,有一个线程读,两个线程写,这时可以有一个特殊的实现能够达到很高的效率吗?作者折腾了一番,找到了一个方法。

原理如下图所示。 (图片后面再补上 :( )

基本原理是,将整个buffer分成两份,两个写线程分别写入其中的一部分。这样就避免了两个写线程之间的冲突。而避免读线程和写线程之间冲突的原理,则和之前的博客中的原理相同,也就是,写线程只修改tail的值,而读线程只修改head的值。这样,就不会出现数据还没读就被覆盖,或者数据还没写就被读出的情况了。

这样的实现有一些缺点。一个是空间利用率不够高,会有浪费,因为有可能一部分写满了而另外一部分还空着;其次,是不能保证读出的顺序和写入的顺序是一致的。不过,实际上有两个线程写的时候,这点本来就不重要。没办法保证那个线程先写,哪个后写。最后,在这个实现中,是buffer的两个部分轮流读数据。这个策略可以根据两个写线程的数据速率进行调整。

但是,这个实现有一个最大的好处,就是速度快。同样没有采用原子加减操作,而只是普通的加减操作。因此实现了很高的运行速度。在符合两个写线程,一个读线程,并且对运行速度有很高要求的场合中,这个实现是一个很好的选择。

最后,附上代码。代码同样可以在github上找到
https://github.com/drneverend/buffers/blob/master/ringbuffer/RingBuffer1r2w.java

 1 public class RingBuffer {
 2     private final static int bufferSize = 1024;
 3     private final static int halfBufferSize = bufferSize / 2;
 4     private String[] buffer = new String[bufferSize];
 5     private int head1 = 0;
 6     private int tail1 = 0;
 7     private int head2 = 0;
 8     private int tail2 = 0;
 9     private int nextReadBuffer = 0;
10
11     private Boolean empty1() {
12         return head1 == tail1;
13     }
14     private Boolean empty2() {
15         return head2 == tail2;
16     }
17     private Boolean empty() {
18         return empty1() && empty2();
19     }
20     private Boolean full1() {
21         return (tail1 + 1) % halfBufferSize == head1;
22     }
23     private Boolean full2() {
24         return (tail2 + 1) % halfBufferSize == head2;
25     }
26     public Boolean put1(String v) {
27         if (full1()) {
28             return false;
29         }
30         buffer[tail1] = v;
31         tail1 = (tail1 + 1) % halfBufferSize;
32         return true;
33     }
34     public Boolean put2(String v) {
35         if (full2()) {
36             return false;
37         }
38         buffer[tail2 + halfBufferSize] = v;
39         tail2 = (tail2 + 1) % halfBufferSize;
40         return true;
41     }
42     public String get() {
43         if (empty()) {
44             return null;
45         }
46         String result = null;
47         if (nextReadBuffer == 0 && !empty1() || nextReadBuffer == 1 && empty2()) {
48             result = buffer[head1];
49             head1 = (head1 + 1) % halfBufferSize;
50         } else {
51             result = buffer[head2];
52             head2 = (head2 + 1) % halfBufferSize;
53         }
54
55         nextReadBuffer = (nextReadBuffer + 1) % 2;
56
57         return result;
58     }
59 }
时间: 2024-12-26 01:21:15

多个写线程一个读线程的无锁队列实现的相关文章

基于无锁队列和c++11的高性能线程池

基于无锁队列和c++11的高性能线程池线程使用c++11库和线程池之间的消息通讯使用一个简单的无锁消息队列适用于linux平台,gcc 4.6以上 标签: <无> 代码片段(6)[全屏查看所有代码] 1. [代码]lckfree.h ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 // lckfree.h //

聊聊高并发(三十二)实现一个基于链表的无锁Set集合

Set表示一种没有反复元素的集合类,在JDK里面有HashSet的实现,底层是基于HashMap来实现的.这里实现一个简化版本号的Set,有下面约束: 1. 基于链表实现.链表节点依照对象的hashCode()顺序由小到大从Head到Tail排列. 2. 如果对象的hashCode()是唯一的.这个如果实际上是不成立的,这里为了简化实现做这个如果.实际情况是HashCode是基于对象地址进行的一次Hash操作.目的是把对象依据Hash散开.所以可能有多个对象地址相应到一个HashCode.也就是

黑马程序员——JAVA基础之Day24 多线程 ,死锁,线程间通信 ,线程组,线程池,定时器。

------- android培训.java培训.期待与您交流! ---------- Lock()实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作. private Lock lock =new ReentrantLock(); 被锁的代码要用   lock.lock()                lock.unlock()    包括.其中用try   ...finally包围 同步:效率低,如果出现同步嵌套,会出现死锁.  但是安全. 死锁问题:两个或者两个以上

一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁

前续 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(一)——地:起因 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁 平行时空 在复制好上面那一行我就先停下来了,算是先占了个位置,虽然我知道大概要怎么写,不过感觉还是很乱. 我突然想到,既然那么纠结,那么混乱,那么不知所措,我们不如换个视角.记得高中时看过的为数不多的长篇小说<穆斯林的葬礼>,作者是:霍达(女),故事描写了两个发生在不同时代.有着不同的内容却又交错扭结的爱情悲剧,一个是“玉”的故事,一个是“月”

Nah Lock: 一个无锁的内存分配器

概述 我实现了两个完全无锁的内存分配器:_nalloc 和 nalloc.  我用benchmark工具对它们进行了一组综合性测试,并比较了它们的指标值. 与libc(glibc malloc)相比,第一个分配器测试结果很差,但是我从中学到了很多东西,然后我实现了第二个无锁分配器,随着核数增加至30,测试结果线性提高.核数增加至60,测试结果次线性提高,但是仅比tcmalloc好一点. 想要安装,输入命令: git clone ~apodolsk/repo/nalloc,阅读 README文档.

写的一个简单定时器(非独立线程)

//Callback.h #ifndef __CALLBACK_H__ #define __CALLBACK_H__ typedef void (*T_CallBack)(void *); typedef struct { T_CallBack cb; void *obj; }ST_CallBack; int __NewTimer(void* obj, int interval, bool isloop, T_CallBack cb); void __DeleteTimer(int handle

JAVA面试经典合集2:怎样写出一个线程安全的单例模式

怎样写出一个线程安全的单例模式 package com.chendan.mianshi; /** * * * @Description * @author ChenDan [email protected] * @date 2019年8月4日下午8:47:13 * */ public class MianShiTest2 { public static void main(String[] args) { // [email protected] // [email protected] //

在Linux下写一个线程池以及线程池的一些用法和注意点

-->线程池介绍(大部分来自网络)  在这个部分,详细的介绍一下线程池的作用以及它的技术背景以及他提供的一些服务等.大部分内容来自我日常生活中在网络中学习到的一些概念性的东西. -->代码(大约240行)  测试一下,具体的实现. -->代码下载 --------------------------------------------------------------------------------------------------------------------------

线程同步——读写锁

读写锁 读写锁与互斥量类似.也是通过加锁的方式来实现线程之间的数据同步.互斥量的特点是 一次只有一个线程对其加锁.而对于度操作来说,即使有多个线程同时进行读操作是不会 产生冲突的.读写锁便利用了这个特性.它一共有三个状态:读模式下加锁状态,写模式 下加锁状态和不加锁状态.使用的规则如下: 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞.但是以读方式枷锁的线程是可以操作共享数据的. 当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权限但是如