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

一文完全掌握 Go math/rand(源碼解析)

 更新時間:2021年04月26日 09:05:21   作者:haohongfan  
這篇文章主要介紹了一文完全掌握 Go math/rand(源碼解析),本文可以幫助大家快速使用Go Rand.,感興趣的朋友跟隨小編一起看看吧

Go 獲取隨機數(shù)是開發(fā)中經常會用到的功能, 不過這個里面還是有一些坑存在的, 本文將完全剖析 Go math/rand, 讓你輕松使用 Go Rand.

開篇一問: 你覺得 rand 會 panic 嗎 ?

源碼剖析

math/rand 源碼其實很簡單, 就兩個比較重要的函數(shù)

func (rng *rngSource) Seed(seed int64) {
	rng.tap = 0
	rng.feed = rngLen - rngTap

	//...
	x := int32(seed)
	for i := -20; i < rngLen; i++ {
		x = seedrand(x)
		if i >= 0 {
			var u int64
			u = int64(x) << 40
			x = seedrand(x)
			u ^= int64(x) << 20
			x = seedrand(x)
			u ^= int64(x)
			u ^= rngCooked[i]
			rng.vec[i] = u
		}
	}
}

這個函數(shù)就是在設置 seed, 其實就是對 rng.vec 各個位置設置對應的值. rng.vec 的大小是 607.

func (rng *rngSource) Uint64() uint64 {
	rng.tap--
	if rng.tap < 0 {
		rng.tap += rngLen
	}

	rng.feed--
	if rng.feed < 0 {
		rng.feed += rngLen
	}

	x := rng.vec[rng.feed] + rng.vec[rng.tap]
	rng.vec[rng.feed] = x
	return uint64(x)
}

我們在使用不管調用 Intn(), Int31n() 等其他函數(shù), 最終調用到就是這個函數(shù). 可以看到每次調用就是利用 rng.feed rng.tap 從 rng.vec 中取到兩個值相加的結果返回了. 同時還是這個結果又重新放入 rng.vec.

在這里需要注意使用 rng.go 的 rngSource 時, 由于 rng.vec 在獲取隨機數(shù)時會同時設置 rng.vec 的值, 當多 goroutine 同時調用時就會有數(shù)據競爭問題. math/rand 采用在調用 rngSource 時加鎖 sync.Mutex 解決.

func (r *lockedSource) Uint64() (n uint64) {
	r.lk.Lock()
	n = r.src.Uint64()
	r.lk.Unlock()
	return
}

另外我們能直接使用 rand.Seed(), rand.Intn(100), 是因為 math/rand 初始化了一個全局的 globalRand 變量.

var globalRand = New(&lockedSource{src: NewSource(1).(*rngSource)})

func Seed(seed int64) { globalRand.Seed(seed) }

func Uint32() uint32 { return globalRand.Uint32() }

需要注意到由于調用 rngSource 加了鎖, 所以直接使用 rand.Int32() 會導致全局的 goroutine 鎖競爭, 所以在高并發(fā)場景時, 當你的程序的性能是卡在這里的話, 你需要考慮利用 New(&lockedSource{src: NewSource(1).(*rngSource)}) 為不同的模塊生成單獨的 rand. 不過根據目前的實踐來看, 使用全局的 globalRand 鎖競爭并沒有我們想象中那么激烈. 使用 New 生成新的 rand 里面是有坑的, 開篇的 panic 就是這么產生的, 后面具體再說.

種子(seed)到底起什么作用 ?

func main() {
	for i := 0; i < 10; i++ {
		fmt.Printf("current:%d\n", time.Now().Unix())
		rand.Seed(time.Now().Unix())
		fmt.Println(rand.Intn(100))
	}
}

結果:

current:1613814632
65
current:1613814632
65
current:1613814632
65
...

這個例子能得出一個結論: 相同種子,每次運行的結果都是一樣的. 這是為什么呢?

在使用 math/rand 的時候, 一定需要通過調用 rand.Seed 來設置種子, 其實就是給 rng.vec 的 607 個槽設置對應的值. 通過上面的源碼那可以看出來, rand.Seed 會調用一個 seedrand 的函數(shù), 來計算對應槽的值.

func seedrand(x int32) int32 {
	const (
		A = 48271
		Q = 44488
		R = 3399
	)

	hi := x / Q
	lo := x % Q
	x = A*lo - R*hi
	if x < 0 {
		x += int32max
	}
	return x
}

這個函數(shù)的計算結果并不是隨機的, 而是根據 seed 實際算出來的. 另外這個函數(shù)并不是隨便寫的, 是有相關的數(shù)學證明的.

這也導致了相同的 seed, 最終設置到 rng.vec里面的值是相同的, 通過 Intn 取出的也是相同的值

我遇到的那些坑

1. rand panic

文章開頭的截圖就是項目開發(fā)中使用別人封裝的底層庫, 在某天出現(xiàn)的 panic. 大概實現(xiàn)的代碼

// random.go

var (
	rrRand = rand.New(rand.NewSource(time.Now().Unix()))
)

type Random struct{}

func (r *Random) Balance(sf *service.Service) ([]string, error) {
	// .. 通過服務發(fā)現(xiàn)獲取到一堆ip+port, 然后隨機拿到其中的一些ip和port出來
	randIndexes := rrRand.Perm(randMax)

	// 返回這些ip 和port
}

這個 Random 會被并發(fā)調用, 由于 rrRand 不是并發(fā)安全的, 所以就導致了調用 rrRand.Perm 時偶爾會出現(xiàn) panic 情況.

在使用 math/rand 的時候, 有些人使用 math.Intn() 看了下注釋發(fā)現(xiàn)是全局共享了一個鎖, 擔心出現(xiàn)鎖競爭, 所以用 rand.New 來初始化一個新的 rand, 但是要注意到 rand.New 初始化出來的 rand 并不是并發(fā)安全的.

修復方案: 就是把 rrRand 換成了 globalRand, 在線上高并發(fā)場景下, 發(fā)現(xiàn)全局鎖影響并不大.

2. 獲取的都是同一個機器

同樣也是底層封裝的 rpc 庫, 使用 random 的方式來流量分發(fā), 在線上跑了一段時間后, 流量都路由到一臺機器上了, 導致服務直接宕機. 大概實現(xiàn)代碼:

func Call(ctx *gin.Context, method string, service string, data map[string]interface{}) (buf []byte, err error) {
	ins, err := ral.GetInstance(ctx, ral.TYPE_HTTP, service)
	if err != nil {
		// 錯誤處理
	}
	defer ins.Release()

	if b, e := ins.Request(ctx, method, data, head); e == nil {
		// 錯誤處理
	}
	// 其他邏輯, 重試等等
}

func GetInstance(ctx *gin.Context, modType string, name string) (*Instance, error) {
	// 其他邏輯..

	switch res.Strategy {
	case WITH_RANDOM:
		if res.rand == nil {
			res.rand = rand.New(rand.NewSource(time.Now().Unix()))
		}
		which = res.rand.Intn(res.count)
	case 其他負載均衡查了
	}

	// 返回其中一個ip和port
}

引起問題的原因: 可以看出來每次請求到來都是利用 GetInstance 來獲取一個 ip 和 port, 如果采用 Random 方式的流量負載均衡, 每次都是重新初始化一個 rand. 我們已經知道當設置相同的種子,每次運行的結果都是一樣的. 當瞬間流量過大時, 并發(fā)請求 GetInstance, 由于那一刻 time.Now().Unix() 的值是一樣的, 這樣就會導致獲取到隨機數(shù)都是一樣的, 所以就導致最后獲取到的 ip, port都是一樣的, 流量都分發(fā)到這臺機器上了.

修復方案: 修改成 globalRand 即可.

rand 未來期望

說到這里基本上可以看出來, 為了防止全局鎖競爭問題, 在使用 math/rand 的時候, 首先都會想到自定義 rand, 但是就容易整出來莫名其妙的問題.

為什么 math/rand 需要加鎖呢?

大家都知道 math/rand 是偽隨機的, 但是在設置完 seed 后, rng.vec 數(shù)組的值基本上就確定下來了, 這明顯就不是隨機了, 為了增加隨機性, 通過 Uint64() 獲取到隨機數(shù)后, 還會重新去設置 rng.vec. 由于存在并發(fā)獲取隨機數(shù)的需求, 也就有了并發(fā)設置 rng.vec 的值, 所以需要對 rng.vec 加鎖保護.

使用 rand.Intn() 確實會有全局鎖競爭問題, 你覺得 math/rand 未來會優(yōu)化嗎? 以及如何優(yōu)化? 歡迎留言討論

到此這篇關于一文完全掌握 Go math/rand(源碼解析)的文章就介紹到這了,更多相關Go math rand內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go語言Http調用之Post請求詳解

    Go語言Http調用之Post請求詳解

    前文我們介紹了如何進行 HTTP 調用,并通過 GET 請求的例子,講述了 query 參數(shù)和 header 參數(shù)如何設置,以及響應體的獲取方法。 本文繼上文,接下來會通過 POST 請求,對其他參數(shù)的設置進行介紹,感興趣的可以了解一下
    2022-12-12
  • Go并發(fā)原語之SingleFlight請求合并方法實例

    Go并發(fā)原語之SingleFlight請求合并方法實例

    本文我們來學習一下 Go 語言的擴展并發(fā)原語:SingleFlight,SingleFlight 的作用是將并發(fā)請求合并成一個請求,以減少重復的進程來優(yōu)化 Go 代碼
    2023-12-12
  • 一步步教你在Linux上安裝Go語言環(huán)境

    一步步教你在Linux上安裝Go語言環(huán)境

    本文將介紹如何在Linux操作系統(tǒng)下搭建Go語言環(huán)境,Go語言是一種開源的編程語言,具有高效、簡潔和并發(fā)性強的特點,適用于開發(fā)各種類型的應用程序,搭建Go語言環(huán)境是開始學習和開發(fā)Go語言項目的第一步,本文將詳細介紹安裝Go語言、配置環(huán)境變量以及驗證安裝是否成功的步驟
    2023-10-10
  • 利用Golang如何調用Linux命令詳解

    利用Golang如何調用Linux命令詳解

    這篇文章主要給大家介紹了Golang中使用os/exec來執(zhí)行 Linux 命令的相關資料,文中給出了詳細的示例代碼,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
    2017-05-05
  • Go語言學習之結構體和方法使用詳解

    Go語言學習之結構體和方法使用詳解

    這篇文章主要為大家詳細介紹了Go語言中結構體和方法的使用,文中的示例代碼講解詳細,對我們學習Go語言有一定的幫助,需要的可以參考一下
    2022-04-04
  • Go?結構體序列化的實現(xiàn)

    Go?結構體序列化的實現(xiàn)

    本文主要介紹了Go?結構體序列化的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 基于Golang編寫貪吃蛇游戲

    基于Golang編寫貪吃蛇游戲

    這篇文章主要為大家學習介紹了Golang如何基于終端庫termbox-go做個功能較簡單的貪吃蛇游戲,文中的示例代碼講解詳細,具有一定的學習價值
    2023-07-07
  • Web框架Gin中間件實現(xiàn)原理步驟解析

    Web框架Gin中間件實現(xiàn)原理步驟解析

    這篇文章主要為大家介紹了Web框架Gin中間件實現(xiàn)原理步驟解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-10-10
  • Golang結構化日志包log/slog的使用詳解

    Golang結構化日志包log/slog的使用詳解

    官方提供的用于打印日志的包是標準庫中的 log 包,該包雖然被廣泛使用,但是缺點也很多,所以Go 1.21新增的 log/slog 完美解決了以上問題,下面我們就來看看log/slog包的使用吧
    2023-09-09
  • Go事務中止時是否真的結束事務解析

    Go事務中止時是否真的結束事務解析

    這篇文章主要為大家介紹了Go事務中止時是否真的結束事務實例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04

最新評論