最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Golang如何實(shí)現(xiàn)一個(gè)環(huán)形緩沖器

 更新時(shí)間:2022年09月02日 16:04:49   作者:jiaxwu  
環(huán)形緩沖器(ringr?buffer)是一種用于表示一個(gè)固定尺寸、頭尾相連的緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu),適合緩存數(shù)據(jù)流。本文將利用Golang實(shí)現(xiàn)一個(gè)環(huán)形緩沖器,需要的可以參考一下

背景

環(huán)形緩沖器(ringr buffer)是一種用于表示一個(gè)固定尺寸、頭尾相連的緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu),適合緩存數(shù)據(jù)流。

在使用上,它就是一個(gè)固定長(zhǎng)度的FIFO隊(duì)列:

在邏輯上,我們可以把它當(dāng)成是一個(gè)環(huán),上面有兩個(gè)指針代表當(dāng)前寫索引和讀索引:

在實(shí)現(xiàn)上,我們一般是使用一個(gè)數(shù)組去實(shí)現(xiàn)這個(gè)環(huán),當(dāng)索引到達(dá)數(shù)組尾部的時(shí)候,則重新設(shè)置為頭部:

kfifo實(shí)現(xiàn)

kfifo是Linux內(nèi)核的隊(duì)列實(shí)現(xiàn),它具有以下特性:

  • 固定長(zhǎng)度:長(zhǎng)度是固定的,而且是向上取最小的2的平方,主要是為了實(shí)現(xiàn)快速取余。
  • 無(wú)鎖:在單生產(chǎn)者和單消費(fèi)者的情況下,是不需要加鎖的。主要是因?yàn)樗饕齣n和out是不回退的,一直往前。
  • 快速取余:我們都直到到達(dá)隊(duì)列末尾的時(shí)候,索引需要回退到開(kāi)頭。最簡(jiǎn)單的實(shí)現(xiàn)方式就是對(duì)索引取余,比如索引in現(xiàn)在是8,隊(duì)列長(zhǎng)度是8,in%len(q)即可回退到開(kāi)頭,但是取余操作%還是比較耗時(shí)的,因此kfifo使用in&mask實(shí)現(xiàn)快速取余,其中mask=len(q)-1。

無(wú)鎖

上面我們說(shuō)到,這個(gè)無(wú)鎖是有條件的,也就是必須在單生產(chǎn)者單消費(fèi)者情況下。這種情況下,同一時(shí)刻最多只可能會(huì)有一個(gè)寫操作和一個(gè)讀操作。但是在某一個(gè)讀操作(或?qū)懖僮鳎┑钠陂g,可能會(huì)有多個(gè)寫操作(或讀操作)發(fā)生。

因?yàn)樗饕齣n和out是不回退的,因此in一直會(huì)在out前面(或者重合)。而且in只被寫操作修改,out只被讀操作修改,因此不會(huì)沖突。

這里可能有人會(huì)擔(dān)心索引溢出的問(wèn)題,比如in到達(dá)math.MaxUint64,再+1則回到0。但是其實(shí)并不影響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
}

當(dāng)然如果連續(xù)兩次溢出,就會(huì)出現(xiàn)問(wèn)題。但是由于數(shù)組長(zhǎng)度是int類型,因此也沒(méi)辦法超過(guò)math.MaxUint64,也就是in和out之間的距離最多也就是2^62,因?yàn)?code>math.MaxInt64是2^63-1,沒(méi)辦法向上取2的平方了。因此也不會(huì)出現(xiàn)溢出兩倍math.MaxUint64的情況,早在溢出之前就隊(duì)列滿了。

快速取余

前面提到取余是通過(guò)in&mask實(shí)現(xiàn)的,這有一個(gè)前提條件,也就是長(zhǎng)度必須是2的次方,因此在創(chuàng)建數(shù)組的時(shí)候,長(zhǎng)度會(huì)向上取最小的2的平方。例如一個(gè)長(zhǎng)度為8的kfifo,在二進(jìn)制表示下:

len  = 0000 1000 // 十進(jìn)制8,隊(duì)列長(zhǎng)度
mask = 0000 0111 // 十進(jìn)制7,掩碼

in   = 0000 0000 // 十進(jìn)制0,寫索引
in & mask => 0000 0000 // 十進(jìn)制0,使用 & mask
in % len  => 0000 0000 // 十進(jìn)制0,使用 % len

in         = 0000 0001 // 十進(jìn)制1,寫索引
in & mask => 0000 0001 // 十進(jìn)制1,使用 & mask
in % len  => 0000 0001 // 十進(jìn)制1,使用 % len

in         = 0000 0001 // 十進(jìn)制1,寫索引
in & mask => 0000 0001 // 十進(jìn)制1,使用 & mask
in % len  => 0000 0001 // 十進(jìn)制1,使用 % len

in         = 0000 1000 // 十進(jìn)制8,寫索引
in & mask => 0000 0000 // 十進(jìn)制0,使用 & mask
in % len  => 0000 0000 // 十進(jìn)制0,使用 % len

in         = 0001 0001 // 十進(jìn)制17,寫索引
in & mask => 0000 0001 // 十進(jìn)制1,使用 & mask
in % len  => 0000 0001 // 十進(jìn)制1,使用 % len

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

然后我們做一個(gè)簡(jiǎn)單的性能測(cè)試:

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
	}
}

測(cè)試結(jié)果:

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

可以看到& mask性能確實(shí)比% len好很多,這也就是為什么要用& Mask來(lái)實(shí)現(xiàn)取余的原因了。

數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)結(jié)構(gòu)和上面介紹的一樣,in、out標(biāo)識(shí)當(dāng)前讀寫的位置;mask是size-1,用于取索引,比%size更加高效;

type Ring[T any] struct {
	in   uint64 // 寫索引
	out  uint64 // 讀索引
	mask uint64 // 掩碼,用于取索引,代替%size
	size uint64 // 長(zhǎng)度
	data []T    // 數(shù)據(jù)
}

Push()

Push()操作很簡(jiǎn)單,首先r.in & r.mask得到寫索引,讓寫索引前進(jìn)一格,然后存入數(shù)據(jù)。

// 插入元素到隊(duì)尾
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()操作同理,根據(jù)r.out & r.mask得到讀索引,讓讀索引前進(jìn)一格,然后讀取數(shù)據(jù)。

// 彈出隊(duì)頭元素
func (r *Ring[T]) Pop() T {
	if r.Empty() {
		panic("ring emtpy")
	}
	out := r.out & r.mask
	r.out++
	return r.data[out]
}

性能測(cè)試

Round實(shí)現(xiàn)是使用& mask,同時(shí)長(zhǎng)度會(huì)向上取2的平方;Fix實(shí)現(xiàn)是使用% size保持參數(shù)的長(zhǎng)度。

測(cè)試代碼是不斷的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()
		}
	}
}

測(cè)試結(jié)果:& mask的性能明顯好于% size

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

無(wú)界環(huán)形緩沖器

我們可以在寫數(shù)據(jù)的時(shí)候判斷是否空間已滿,如果已滿我們可以進(jìn)行動(dòng)態(tài)擴(kuò)容,從而實(shí)現(xiàn)一個(gè)無(wú)界環(huán)形緩沖器。

Push()

在Push()時(shí)檢查到空間滿時(shí),調(diào)用grow()擴(kuò)展空間即可:

// 插入元素到隊(duì)尾
func (r *Ring[T]) Push(e T) {
	if r.Full() {
                // 擴(kuò)展空間
		r.Grow(r.Cap() + 1)
	}
	in := r.in % r.size
	r.in++
	r.data[in] = e
}

grow()

擴(kuò)容一般是擴(kuò)展為當(dāng)前容量的兩倍,然后把原來(lái)數(shù)據(jù)copy()到新的數(shù)組,更新字段即可:

// 擴(kuò)容
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
	}
	// 還沒(méi)容量,直接申請(qǐng),因?yàn)椴恍枰w移元素
	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
}

線程安全性

由于可能會(huì)動(dòng)態(tài)擴(kuò)容,需要修改out、in指針,因此需要加鎖保證安全。

代碼地址

https://github.com/jiaxwu/gommon/tree/main/container/ringbuffer

到此這篇關(guān)于詳解Golang如何實(shí)現(xiàn)一個(gè)環(huán)形緩沖器的文章就介紹到這了,更多相關(guān)Golang環(huán)形緩沖器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang?sync.Map底層實(shí)現(xiàn)場(chǎng)景示例詳解

    Golang?sync.Map底層實(shí)現(xiàn)場(chǎng)景示例詳解

    這篇文章主要為大家介紹了Golang?sync.Map底層實(shí)現(xiàn)及使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • go解析YAML文件(多文檔解析)

    go解析YAML文件(多文檔解析)

    本文介紹了如何使用GO語(yǔ)言和client-go庫(kù)處理YAML文件,特別是在Kubernetes環(huán)境下,分析了YAML的特點(diǎn),如簡(jiǎn)潔性、易讀性、可嵌套性等,并展示了相關(guān)代碼實(shí)現(xiàn),包括單文檔和多文檔的處理方法,感興趣的可以了解一下
    2024-10-10
  • 一文了解golang 占位符

    一文了解golang 占位符

    本文主要介紹了一文了解golang 占位符,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • golang多次讀取http request body的問(wèn)題分析

    golang多次讀取http request body的問(wèn)題分析

    這篇文章主要給大家分析了golang多次讀取http request body的問(wèn)題,文中通過(guò)代碼示例和圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-01-01
  • go web 預(yù)防跨站腳本的實(shí)現(xiàn)方式

    go web 預(yù)防跨站腳本的實(shí)現(xiàn)方式

    這篇文章主要介紹了go web 預(yù)防跨站腳本的實(shí)現(xiàn)方式,文中給大家介紹XSS最佳的防護(hù)應(yīng)該注意哪些問(wèn)題,本文通過(guò)實(shí)例代碼講解的非常詳細(xì),需要的朋友可以參考下
    2021-06-06
  • 手把手帶你走進(jìn)Go語(yǔ)言之條件表達(dá)式

    手把手帶你走進(jìn)Go語(yǔ)言之條件表達(dá)式

    條件表達(dá)式由條件運(yùn)算符構(gòu)成,并常用條件表達(dá)式構(gòu)成一個(gè)賦值語(yǔ)句,本文給大家介紹了在Go語(yǔ)言中條件表達(dá)式的具體用法,講述的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值
    2021-09-09
  • Golang中函數(shù)(Function)和方法(Method)的區(qū)別詳解

    Golang中函數(shù)(Function)和方法(Method)的區(qū)別詳解

    在Golang中,大家必然會(huì)頻繁使用到函數(shù)(Function)和方法(Method),但是有的同學(xué)可能并沒(méi)有注意過(guò)函數(shù)和方法的異同點(diǎn),函數(shù)和方法都是用來(lái)執(zhí)行特定任務(wù)的代碼塊,雖然很相似,但也有很大的區(qū)別,所以本文將詳細(xì)講解函數(shù)和方法的定義以及它們的異同點(diǎn)
    2023-07-07
  • Go語(yǔ)言Mock使用基本指南詳解

    Go語(yǔ)言Mock使用基本指南詳解

    這篇文章主要介紹了Go語(yǔ)言Mock使用基本指南詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • 淺析Golang如何利用泛型編寫更安全的代碼

    淺析Golang如何利用泛型編寫更安全的代碼

    從Go 1.18正式引入泛型,再到Go 1.21大量泛型函數(shù)/類型進(jìn)入標(biāo)準(zhǔn)庫(kù)開(kāi)始已經(jīng)過(guò)去了三年,這篇文章要說(shuō)的是泛型在強(qiáng)化代碼安全性和健壯性方面的應(yīng)用,感興趣的可以了解下
    2025-12-12
  • Golang報(bào)“import cycle not allowed”錯(cuò)誤的2種解決方法

    Golang報(bào)“import cycle not allowed”錯(cuò)誤的2種解決方法

    這篇文章主要給大家介紹了關(guān)于Golang報(bào)"import cycle not allowed"錯(cuò)誤的2種解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以們下面隨著小編來(lái)一起看看吧
    2018-08-08

最新評(píng)論

五常市| 克什克腾旗| 盱眙县| 比如县| 大田县| 虹口区| 成安县| 崇州市| 岳普湖县| 务川| 浦北县| 江西省| 金坛市| 望谟县| 沐川县| 乌兰浩特市| 靖宇县| 建瓯市| 贵溪市| 营山县| 南汇区| 新巴尔虎左旗| 余江县| 苏尼特右旗| 塘沽区| 手游| 辽阳市| 无为县| 铁岭县| 瓮安县| 凤庆县| 岳池县| 双牌县| 延寿县| 成安县| 增城市| 宜城市| 汾西县| 深州市| 马山县| 札达县|