返回顶部
首页 > 资讯 > 后端开发 > GO >详解Golang如何实现一个环形缓冲器
  • 183
分享到

详解Golang如何实现一个环形缓冲器

2024-04-02 19:04:59 183人浏览 薄情痞子
摘要

目录背景kfifo实现无锁快速取余数据结构Push()Pop()性能测试无界环形缓冲器Push()grow()线程安全性代码地址背景 环形缓冲器(ringr buffer)是一种用于

背景

环形缓冲器(ringr buffer)是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。

在使用上,它就是一个固定长度的FIFO队列:

在逻辑上,我们可以把它当成是一个环,上面有两个指针代表当前写索引和读索引:

在实现上,我们一般是使用一个数组去实现这个环,当索引到达数组尾部的时候,则重新设置为头部:

kfifo实现

kfifo是linux内核的队列实现,它具有以下特性:

  • 固定长度:长度是固定的,而且是向上取最小的2的平方,主要是为了实现快速取余。
  • 无锁:在单生产者和单消费者的情况下,是不需要加锁的。主要是因为索引in和out是不回退的,一直往前。
  • 快速取余:我们都直到到达队列末尾的时候,索引需要回退到开头。最简单的实现方式就是对索引取余,比如索引in现在是8,队列长度是8,in%len(q)即可回退到开头,但是取余操作%还是比较耗时的,因此kfifo使用in&mask实现快速取余,其中mask=len(q)-1

无锁

上面我们说到,这个无锁是有条件的,也就是必须在单生产者单消费者情况下。这种情况下,同一时刻最多只可能会有一个写操作和一个读操作。但是在某一个读操作(或写操作)的期间,可能会有多个写操作(或读操作)发生。

因为索引in和out是不回退的,因此in一直会在out前面(或者重合)。而且in只被写操作修改,out只被读操作修改,因此不会冲突。

这里可能有人会担心索引溢出的问题,比如in到达math.MaxUint64,再+1则回到0。但是其实并不影响in和out之间的距离:

package main

import (
	"fmt"
	"math"
)

func main() {
	var in uint = math.MaxUint64
	var out uint = math.MaxUint64 - 1
	fmt.Println(in - out) // 1
	in++
	fmt.Println(in - out) // 2
	out++
	fmt.Println(in - out) // 1
}

当然如果连续两次溢出,就会出现问题。但是由于数组长度是int类型,因此也没办法超过math.MaxUint64,也就是in和out之间的距离最多也就是2^62,因为math.MaxInt642^63-1,没办法向上取2的平方了。因此也不会出现溢出两倍math.MaxUint64的情况,早在溢出之前就队列满了。

快速取余

前面提到取余是通过in&mask实现的,这有一个前提条件,也就是长度必须是2的次方,因此在创建数组的时候,长度会向上取最小的2的平方。例如一个长度为8的kfifo,在二进制表示下:

len  = 0000 1000 // 十进制8,队列长度
mask = 0000 0111 // 十进制7,掩码

in   = 0000 0000 // 十进制0,写索引
in & mask => 0000 0000 // 十进制0,使用 & mask
in % len  => 0000 0000 // 十进制0,使用 % len

in         = 0000 0001 // 十进制1,写索引
in & mask => 0000 0001 // 十进制1,使用 & mask
in % len  => 0000 0001 // 十进制1,使用 % len

in         = 0000 0001 // 十进制1,写索引
in & mask => 0000 0001 // 十进制1,使用 & mask
in % len  => 0000 0001 // 十进制1,使用 % len

in         = 0000 1000 // 十进制8,写索引
in & mask => 0000 0000 // 十进制0,使用 & mask
in % len  => 0000 0000 // 十进制0,使用 % len

in         = 0001 0001 // 十进制17,写索引
in & mask => 0000 0001 // 十进制1,使用 & mask
in % len  => 0000 0001 // 十进制1,使用 % len

可以看到,使用& mask的效果是和% len一样的。

然后我们做一个简单的性能测试

package main

import "testing"

var (
	Len  = 8
	Mask = Len - 1
	In   = 8 - 5
)

// % len
func BenchmarkModLen(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = In % Len
	}
}

// & Mask
func BenchmarkAndMask(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = In & Mask
	}
}

测试结果:

BenchmarkModLen-8       1000000000               0.3434 ns/op
BenchmarkAndMask-8      1000000000               0.2520 ns/op

可以看到& mask性能确实比% len好很多,这也就是为什么要用& Mask来实现取余的原因了。

数据结构

数据结构和上面介绍的一样,in、out标识当前读写的位置;mask是size-1,用于取索引,比%size更加高效;

type Ring[T any] struct {
	in   uint64 // 写索引
	out  uint64 // 读索引
	mask uint64 // 掩码,用于取索引,代替%size
	size uint64 // 长度
	data []T    // 数据
}

Push()

Push()操作很简单,首先r.in & r.mask得到写索引,让写索引前进一格,然后存入数据。

// 插入元素到队尾
func (r *Ring[T]) Push(e T) {
	if r.Full() {
		panic("ring full")
	}
	in := r.in & r.mask
	r.in++
	r.data[in] = e
}

Pop()

Pop()操作同理,根据r.out & r.mask得到读索引,让读索引前进一格,然后读取数据。

// 弹出队头元素
func (r *Ring[T]) Pop() T {
	if r.Empty() {
		panic("ring emtpy")
	}
	out := r.out & r.mask
	r.out++
	return r.data[out]
}

性能测试

Round实现是使用& mask,同时长度会向上取2的平方;Fix实现是使用% size保持参数的长度。

测试代码是不断的Push()然后Pop():

func BenchmarkRoundPushPop(b *testing.B) {
	for i := 0; i < b.N; i++ {
		r := New[int](RoundFixSize)
		for j := 0; j < RoundFixSize; j++ {
			r.Push(j)
		}
		for j := 0; j < RoundFixSize; j++ {
			r.Pop()
		}
	}
}

测试结果:& mask的性能明显好于% size

BenchmarkRoundPushPop-8             2544            405621 ns/op // & mask
BenchmarkFixPushPop-8                678           1740489 ns/op // % size

无界环形缓冲器

我们可以在写数据的时候判断是否空间已满,如果已满我们可以进行动态扩容,从而实现一个无界环形缓冲器。

Push()

在Push()时检查到空间满时,调用grow()扩展空间即可:

// 插入元素到队尾
func (r *Ring[T]) Push(e T) {
	if r.Full() {
                // 扩展空间
		r.Grow(r.Cap() + 1)
	}
	in := r.in % r.size
	r.in++
	r.data[in] = e
}

grow()

扩容一般是扩展为当前容量的两倍,然后把原来数据copy()到新的数组,更新字段即可:

// 扩容
func (r *Ring[T]) Grow(minSize uint64) {
	size := mmath.Max(r.size*2, minSize)
	if size > MaxSize {
		panic("size is too large")
	}
	if size < 2 {
		size = 2
	}
	// 还没容量,直接申请,因为不需要迁移元素
	if r.size == 0 {
		r.data = make([]T, size)
		r.size = size
		return
	}
	data := make([]T, size)
	out := r.out % r.size
	len := r.Len()
	copied := copy(data[:len], r.data[out:])
	copy(data[copied:len], r.data)
	r.out = 0
	r.in = len
	r.size = size
	r.data = data
}

线程安全性

由于可能会动态扩容,需要修改out、in指针,因此需要加锁保证安全。

代码地址

https://GitHub.com/jiaxwu/Gommon/tree/main/container/ringbuffer

到此这篇关于详解golang如何实现一个环形缓冲器的文章就介绍到这了,更多相关Golang环形缓冲器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: 详解Golang如何实现一个环形缓冲器

本文链接: https://lsjlt.com/news/121167.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 详解Golang如何实现一个环形缓冲器
    目录背景kfifo实现无锁快速取余数据结构Push()Pop()性能测试无界环形缓冲器Push()grow()线程安全性代码地址背景 环形缓冲器(ringr buffer)是一种用于...
    99+
    2024-04-02
  • css3如何实现一个梯形出来
    这篇文章主要介绍“css3如何实现一个梯形出来”,在日常操作中,相信很多人在css3如何实现一个梯形出来问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”css3如何实现一个梯形...
    99+
    2024-04-02
  • Vue下如何实现一个树形组件
    这篇文章主要介绍“Vue下如何实现一个树形组件”,在日常操作中,相信很多人在Vue下如何实现一个树形组件问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue下如何实现一个树形组件”的疑惑有所帮助!接下来,请跟...
    99+
    2023-07-04
  • Android如何实现一个DocumentProvider示例详解
    目录前言步骤首先在Manifest 中注册这个Provider创建这个Provider重写queryRoot重写queryDocument重写getChildDocument前言 假...
    99+
    2022-12-25
    Android实现DocumentProvider Android DocumentProvider
  • Canvas如何实现一个六边形能力图
    这篇文章将为大家详细讲解有关Canvas如何实现一个六边形能力图,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、前言六边形能力图如下,由 6 个 六边形组成,每一个顶点代表其在某一方面的能力。这篇文章我...
    99+
    2023-06-09
  • 详解如何实现一个Kotlin函数类型
    目录接口与函数类型总结接口与函数类型 业务开发中,经常会有实现一个函数式接口(即接口只有一个方法需要实现)的场景,大家应该都会不假思索的写出如下代码: interface Callb...
    99+
    2022-11-13
    实现Kotlin函数类型 Kotlin函数类型 Kotlin函数
  • react如何实现一个密码强度检测器详解
    目录前言使用组件编写数据结构解析流程解析底层代码解析其他总结前言 密码强度文件校验器; 注册帐号的时候我们需要对用户当前的密码强度进行一个评估,这个过程我们需要做一个检测器,最好写的...
    99+
    2024-04-02
  • 详解Golang如何优雅的终止一个服务
    目录前言1 Shutdown 方法2 signal.Notify 函数3 Server 优雅的终止总结前言 采用常规方式启动一个 Golang http 服务时,若服务被意外终止或中...
    99+
    2024-04-02
  • 详解如何利用C++实现一个反射类
    目录正题开始Singleton.h反射类Reflex.h代码环境为VScode + CMake + GCC 8.1.0 首先,如何才能做到给我一个名字我就能拿到这个对象的这个值,也是...
    99+
    2023-03-09
    C++实现反射类 C++反射类
  • Golang如何实现一个不可复制类型
    这篇文章主要讲解了“Golang如何实现一个不可复制类型”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang如何实现一个不可复制类型”吧!如何复制一个对象不考虑IDE提供的代码分析和g...
    99+
    2023-07-05
  • Golang拾遗之实现一个不可复制类型详解
    目录如何复制一个对象为什么要禁止复制运行时检测实现禁止复制初步尝试更好的实现性能优点和缺点静态检测实现禁止复制利用Locker接口不可复制实现静态检测优点和缺点更进一步利用packa...
    99+
    2023-02-20
    Golang实现不可复制类型 Golang不可复制类型 Go 不可复制类型
  • C语言如何实现一个约瑟夫环
    本篇内容主要讲解“C语言如何实现一个约瑟夫环”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言如何实现一个约瑟夫环”吧!C语言约瑟夫环的实现一、典故:   &nb...
    99+
    2023-06-17
  • Golang如何快速构建一个CLI小工具详解
    目录如何Golang快速构建一个CLI小工具先上效果那么如何实现呢?如何Golang快速构建一个CLI小工具 在现实开发的过程中,大家会发现很多开源的框架都会有着自己的一个CLI工具...
    99+
    2024-04-02
  • 详解如何实现一个简单的Node.js脚手架
    原因 在工作中,需要开发一个脚手架,用于给相关用户提供相关的开发便利性。 适合人群 对前端、Node操作有一定的了解,同时向了解脚手架开发过程或者需要自己实现一个脚手架的开发者。 目标 开发一个简...
    99+
    2022-06-04
    脚手架 如何实现 详解
  • golang如何实现一个restful微服务的操作
    这篇文章将为大家详细讲解有关golang如何实现一个restful微服务的操作,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。什么是golanggolang 是Google开发的一种静态强类型、编译型、并发...
    99+
    2023-06-14
  • Java如何实现一个单向非循环链表
    这篇文章主要介绍“Java如何实现一个单向非循环链表”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java如何实现一个单向非循环链表”文章能帮助大家解决问题。1、什么是链表?链表是一种物理存储结构上...
    99+
    2023-07-04
  • 如何实现Python调用Golang代码详解
    1.调用编译后的动态库文件,报头文件错误 原因: 不同平台下编译的add.so 不能通用,Windows下可以运行的so文件,linux下就不能运行,需要重新编译linux的so文件...
    99+
    2023-05-20
    Python调用golang方法 Python调用golang代码
  • 在 Golang 中实现一个简单的Http中间件过程详解
    本文主要针对Golang的内置库 net/http 做了简单的扩展,通过添加中间件的形式实现了管道(Pipeline)模式,这样的好处是各模块之间是低耦合的,符合单一职责原则,可以很...
    99+
    2024-04-02
  • golang如何实现一个高性能的心跳检测
    今天小编给大家分享一下golang如何实现一个高性能的心跳检测的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。使用 Golan...
    99+
    2023-07-05
  • vue如何使用递归组件实现一个树形控件
    这篇文章主要介绍“vue如何使用递归组件实现一个树形控件”,在日常操作中,相信很多人在vue如何使用递归组件实现一个树形控件问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”vue如何使用递归组件实现一个树形控件...
    99+
    2023-07-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作