欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解決Go語言中高頻次和高并發(fā)下隨機數(shù)重復(fù)的問題

 更新時間:2023年12月05日 10:30:34   作者:Effend  
在Golang中,獲取隨機數(shù)的方法一般會介紹有兩種,一種是基于math/rand的偽隨機,一種是基于crypto/rand的真隨機,math/rand由于其偽隨機的原理,經(jīng)常會出現(xiàn)重復(fù)的隨機數(shù),導(dǎo)致在需要進行隨機的業(yè)務(wù)出現(xiàn)較多的重復(fù)問題,所以本文給大家介紹了較好的解放方案

一、概要:

在Golang中,獲取隨機數(shù)的方法一般會介紹有兩種,一種是基于math/rand的偽隨機,一種是基于crypto/rand的真隨機。其中,math/rand由于其偽隨機的原理,經(jīng)常會出現(xiàn)重復(fù)的隨機數(shù),導(dǎo)致在需要進行隨機的業(yè)務(wù)出現(xiàn)較多的重復(fù)問題。在作者所經(jīng)歷的實際項目中便遇到在高頻率和高并發(fā)下獲取隨機數(shù)時的重復(fù)問題,在解決該問題的道路上,嘗試了幾種辦法,最終發(fā)現(xiàn)了較好的解決方案。

二、對math/rand隨機數(shù)的研究:

基于math/rand獲取隨機數(shù),常見的方法是:

import (
	"math/rand"
	"time"
)

r := rand.New(rand.NewSource(time.Now().UnixNano()))
result := r.Int31n(100)

上述代碼原理,是通過納秒時間來生成種子,然后通過該種子獲取一個隨機數(shù)。常用的開發(fā)者應(yīng)該知道,math/rand的種子具有其固定性,同一個數(shù)作為種子時,通過New獲取的實例其實是相同的,即便它們的實例地址不同,但通過同樣的隨機數(shù)方法,獲得的值會是一樣的。

這種情況是由于math/rand會根據(jù)隨機種子進行一個較復(fù)雜的算法過程,從而獲得一個長度為607的隨機數(shù)池,由于算法過程是固定的,不存在任何的隨機情況,因此同一個種子獲取的隨機數(shù)池是完全一致的。

此時可能會有人認為,每次取不同的納秒時間來生成實例,不就可以解決問題?但這樣的想法在實際的項目實踐中是存在問題的。原因主要有兩點:

1、Golang的一大優(yōu)點是速度快,當(dāng)一次性多次使用time.Now().UnixNano()獲取納秒時間時,會發(fā)現(xiàn)納秒時間并不是不同的,而會存在重復(fù),這使得在高頻率隨機下隨機種子是一樣的。

2、Golang的一大特點是高并發(fā),但是在實際的運行過程中,是無法保證兩個并發(fā)的協(xié)程不會在同一時間獲取時間的,同樣會出現(xiàn)重復(fù)問題。

針對這兩個問題也有對應(yīng)的解決方案:

1、對于第一個原因,可以在每次獲取納秒時間前,使用time.Sleep(time.Nanosecond)保證程序過一個時間再取,如此可以保證每次獲取的時間是不重復(fù)的。

2、對于第二個原因,可以利用鎖將獲取時間的過程鎖住,以保證并發(fā)時不在同一時間執(zhí)行。

然而在實際項目中實踐的情況并不理想。

第一個解決方案下,由于CPU處理的時間片,跳過的時間并不是1納秒,不同的CPU會有微小差異。作者在i7-13700K的CPU下測試,跳過前后的時間差大約為0.1ms。

第二個解決方案下,倘若不結(jié)合第一個解決方案,因為無法保證鎖前和鎖后的時間是否完全不一的情況,因此需要保留sleep過程,然而可以計算的是,若并發(fā)量達到了10000,僅是獲取種子的最長等待時間就可能達到1s,加上一般還會有其它業(yè)務(wù)邏輯,這難以保證高效的需求。

上述方案均存在隱含的問題,那么換一個解決思路,既然隨機數(shù)池是固定長度,不能使用sleep或鎖,為什么不讓程序只保留一個隨機實例并當(dāng)獲取次數(shù)達到一定值時更新隨機實例呢?我們使用下面這個簡單例子來測試可行性:

import (
	"math/rand"
	"time"
	"fmt"
)


t1 := time.Now().UnixNano()
fmt.Println(t1)
r := rand.New(rand.NewSource(t1))
for i := 0; i < 600; i++ {
	r.Int31n(100)
}
t2 := time.Now().UnixNano()
fmt.Println(t2)

上述代碼中,我們通過t1時間生成一個隨機實例,并讓該實例隨機600次,再獲取一次時間t2。執(zhí)行的結(jié)果會發(fā)現(xiàn)更大的問題,t1時間和t2時間是完全相同的!此時如果用t2作為種子,那么下一次隨機的600次會和t1時間的一模一樣。假如我們讓不同實例之間做延時或鎖,那么問題又會回到前面解決方案中同樣的問題。

因此,可以做出如下的結(jié)論。常用的math/rand在基于獲取當(dāng)前時間作為種子的隨機時,無法真正地解決重復(fù)問題。

三、對crypto/rand隨機數(shù)的研究:

基于crypto/rand獲取隨機數(shù),常見的方法是:

import (
	"crypto/rand"
	"math/big"
)

r, _ := rand.Int(rand.Reader, big.NewInt(100))
result := r.Int64()

crypto/rand獲取的隨機數(shù)可以看做是真隨機,其原因是它獲取種子的來源。簡單來說,在各類系統(tǒng)中,均存在一個特定的文件專門用于存儲真隨機值,這些值的來源是硬件在電路上產(chǎn)生的各種信息、熱噪聲等,由于它們是不可控的且無法預(yù)測的,因此可看做是產(chǎn)生了真正的隨機數(shù)。

在上述代碼中,rand.Reader就會從系統(tǒng)中存儲真隨機數(shù)的文件或者通過調(diào)用系統(tǒng)級別的API獲取一個真隨機數(shù)。對于系統(tǒng)而言,當(dāng)該隨機數(shù)被獲取后就會被銷毀,不會二次使用。

然而,crypto/rand有一個缺點,它的獲取效率非常低,這是因為它需要訪問系統(tǒng)文件或者調(diào)用系統(tǒng)API,會產(chǎn)生不小的耗時。并且,每次從系統(tǒng)獲取的隨機值都會是一次性的,無法第二次使用。低效的獲取方式顯然并不能完全滿足高頻次和高并發(fā)下保持程序高效的需求。

四、最后的解決方案:

結(jié)合前面的相關(guān)內(nèi)容,可以找到一個取長補短的方式,得到一個解決方案:利用crypto/rand的rand.Reader獲取一個隨機數(shù),使用這個隨機數(shù)作為math/rand的種子獲得一個隨機數(shù)池進行取值,當(dāng)取值達到一定次數(shù)后,再獲取一個新的種子生成新的實例。

通過rand.Reader獲取一個種子數(shù)的方法如下:

import (
	"crypto/rand"
	"encoding/binary"
)

var seed int64
binary.Read(rand.Reader, binary.BigEndian, &seed)

使用上述方法,即便高頻次或高并發(fā)地獲取種子數(shù),也能保證每次得到的種子都是不一樣的數(shù)值,這樣就能避免因種子相同導(dǎo)致的重復(fù)問題了。

最終的解決方案確定,結(jié)合后的代碼如下:

import (
	crand "crypto/rand"
	"encoding/binary"
	mrand "math/rand"
)

var seed int64
binary.Read(crand.Reader, binary.BigEndian, &seed)
source := mrand.NewSource(seed)
r := mrand.New(source)
result := r.Int31n(100)

在上述代碼中,仍然需要注意一個重要問題,就是crand.Reader的耗時較長,在對取隨機的過程進行封裝時,需要進行進一步的改進:

1、增加一個統(tǒng)計閾值,每次隨機后+1,并當(dāng)次數(shù)達到一定值時更新隨機實例;

2、對更新隨機實例過程上鎖,避免并發(fā)時出現(xiàn)重復(fù)更新;

經(jīng)過改進后,可自行測試其速度。作者參與過的項目實踐中,在統(tǒng)計閾值為300且較高的獲取次數(shù)下,平均每次獲取一個隨機數(shù)的耗時約25~30ns。不僅滿足了接近真隨機的需求,并且滿足了高效的需求。

以上就是解決Go語言中高頻次和高并發(fā)下的隨機數(shù)重復(fù)的問題的詳細內(nèi)容,更多關(guān)于Go高頻次和高并發(fā)隨機數(shù)重復(fù)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 一文帶大家搞懂Go語言中的迭代器

    一文帶大家搞懂Go語言中的迭代器

    迭代器是使用戶可在容器對象上遍訪的對象,設(shè)計人員使用此接口無需關(guān)心容器對象的內(nèi)存分配的實現(xiàn)細節(jié),本文主要為大家詳細介紹一下Go語言中的迭代器的實現(xiàn),需要的可以了解下
    2025-02-02
  • Go使用sync.Map來解決map的并發(fā)操作問題

    Go使用sync.Map來解決map的并發(fā)操作問題

    在 Golang 中 map 不是并發(fā)安全的,sync.Map 的引入確實解決了 map 的并發(fā)安全問題,本文就詳細的介紹一下如何使用,感興趣的可以了解一下
    2021-10-10
  • 使用gorm.Scopes函數(shù)實現(xiàn)復(fù)用查詢邏輯示例

    使用gorm.Scopes函數(shù)實現(xiàn)復(fù)用查詢邏輯示例

    這篇文章主要為大家介紹了使用gorm.Scopes函數(shù)實現(xiàn)復(fù)用查詢邏輯示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Go語言擴展原語之Semaphore的用法詳解

    Go語言擴展原語之Semaphore的用法詳解

    Go語言的擴展包中提供了帶權(quán)重的信號量?semaphore.Weighted,讓我們可以按照不同的權(quán)重管理資源的訪問,下面小編就來和大家聊聊它們的具體用法吧
    2023-07-07
  • Go errors默認加堆棧信息的作用分析

    Go errors默認加堆棧信息的作用分析

    這篇文章主要為大家介紹了Go errors默認加堆棧信息作用分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Go經(jīng)典面試題匯總(填空+判斷)

    Go經(jīng)典面試題匯總(填空+判斷)

    這篇文章主要介紹了Go經(jīng)典面試題匯總(填空+判斷),本文章內(nèi)容詳細,具有很好的參考價值,希望對大家有所幫助,需要的朋友可以參考下
    2023-01-01
  • Go語言包管理工具dep的安裝與使用

    Go語言包管理工具dep的安裝與使用

    godep是解決包依賴的管理工具,下面這篇文章主要給大家介紹了關(guān)于Go語言包管理工具dep的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解

    Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解

    這篇文章主要為大家介紹了Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • Go語言Slice切片底層的實現(xiàn)

    Go語言Slice切片底層的實現(xiàn)

    本文主要介紹了Go語言Slice切片底層的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • go語言time.After()的作用

    go語言time.After()的作用

    time.After 是 Go 語言中的一個函數(shù),用于返回一個定時器通道,該通道在指定時間后發(fā)送當(dāng)前時間,這個功能常用于超時控制和延遲執(zhí)行,本文就來詳細的介紹一下,感興趣的可以了解學(xué)習(xí)
    2024-10-10

最新評論