线程安全的无锁RingBuffer的实现【多个写线程一个读线程】

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

但是,有的情况下并不是只有一个读线程和一个写线程。越是一般化的实现,支持的情况越多,但是往往损失的性能也越多。作者看到过一个实现(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 + halfBufferSize];
52             head2 = (head2 + 1) % halfBufferSize;
53         }
54         
55         nextReadBuffer = (nextReadBuffer + 1) % 2;
56 
57         return result;
58     }
59 }
复制代码
Linux内核中的无锁(lock-free)技术主要用于实现高效的并发数据结构,以提高系统的性能和吞吐量。其中,无锁环形缓冲区(lock-free ring buffer)是一种常用的数据结构,它可以高效地实现多个线程之间传递数据的功能。 无锁环形缓冲区的实现原理如下: 1. 环形缓冲区的数据结构:无锁环形缓冲区由一个固定大小的环形数组和两个指针构成,一个指针,一个指针。指针指向下一个将要取的元素,指针指向下一个将要入的元素。 2. 原子操作:无锁环形缓冲区的实现依赖于原子操作(atomic operations),这些操作是在单个CPU指令中执行的,不会被其他线程中断。在Linux内核中,原子操作是通过宏定义实现的,如“atomic_add()”、“atomic_sub()”等。 3. 入数据:当一个线程想要入数据时,它首先需要检查缓冲区是否已满。如果缓冲区已满,则入操作失败。如果缓冲区未满,则该线程会使用原子操作将数据入缓冲区,并更新指针。 4. 取数据:当一个线程想要取数据时,它首先需要检查缓冲区是否为空。如果缓冲区为空,则取操作失败。如果缓冲区不为空,则该线程会使用原子操作将数据从缓冲区中取,并更新指针。 5. 线程同步:无锁环形缓冲区的实现不依赖于任何锁机制,因此可以避免锁竞争和死锁等问题。不过,在多个线程并发的情况下,需要使用一些同步机制来保证线程安全,如使用原子操作或者memory barrier等技术。 总的来说,无锁环形缓冲区是一种高效的并发数据结构,能够在多个线程之间高效地传递数据,提高系统的性能和吞吐量。在Linux内核中,无锁环形缓冲区的实现依赖于原子操作和线程同步技术,可以避免锁竞争和死锁等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值