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

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

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

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

開篇一問(wèn): 你覺(jué)得 rand 會(huì) panic 嗎 ?

源碼剖析

math/rand 源碼其實(shí)很簡(jiǎn)單, 就兩個(gè)比較重要的函數(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
		}
	}
}

這個(gè)函數(shù)就是在設(shè)置 seed, 其實(shí)就是對(duì) rng.vec 各個(gè)位置設(shè)置對(duì)應(yīng)的值. 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)
}

我們?cè)谑褂貌还苷{(diào)用 Intn(), Int31n() 等其他函數(shù), 最終調(diào)用到就是這個(gè)函數(shù). 可以看到每次調(diào)用就是利用 rng.feed rng.tap 從 rng.vec 中取到兩個(gè)值相加的結(jié)果返回了. 同時(shí)還是這個(gè)結(jié)果又重新放入 rng.vec.

在這里需要注意使用 rng.go 的 rngSource 時(shí), 由于 rng.vec 在獲取隨機(jī)數(shù)時(shí)會(huì)同時(shí)設(shè)置 rng.vec 的值, 當(dāng)多 goroutine 同時(shí)調(diào)用時(shí)就會(huì)有數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題. math/rand 采用在調(diào)用 rngSource 時(shí)加鎖 sync.Mutex 解決.

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

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

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

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

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

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

種子(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))
	}
}

結(jié)果:

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

這個(gè)例子能得出一個(gè)結(jié)論: 相同種子,每次運(yùn)行的結(jié)果都是一樣的. 這是為什么呢?

在使用 math/rand 的時(shí)候, 一定需要通過(guò)調(diào)用 rand.Seed 來(lái)設(shè)置種子, 其實(shí)就是給 rng.vec 的 607 個(gè)槽設(shè)置對(duì)應(yīng)的值. 通過(guò)上面的源碼那可以看出來(lái), rand.Seed 會(huì)調(diào)用一個(gè) seedrand 的函數(shù), 來(lái)計(jì)算對(duì)應(yīng)槽的值.

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
}

這個(gè)函數(shù)的計(jì)算結(jié)果并不是隨機(jī)的, 而是根據(jù) seed 實(shí)際算出來(lái)的. 另外這個(gè)函數(shù)并不是隨便寫的, 是有相關(guān)的數(shù)學(xué)證明的.

這也導(dǎo)致了相同的 seed, 最終設(shè)置到 rng.vec里面的值是相同的, 通過(guò) Intn 取出的也是相同的值

我遇到的那些坑

1. rand panic

文章開頭的截圖就是項(xiàng)目開發(fā)中使用別人封裝的底層庫(kù), 在某天出現(xiàn)的 panic. 大概實(shí)現(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) {
	// .. 通過(guò)服務(wù)發(fā)現(xiàn)獲取到一堆ip+port, 然后隨機(jī)拿到其中的一些ip和port出來(lái)
	randIndexes := rrRand.Perm(randMax)

	// 返回這些ip 和port
}

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

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

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

2. 獲取的都是同一個(gè)機(jī)器

同樣也是底層封裝的 rpc 庫(kù), 使用 random 的方式來(lái)流量分發(fā), 在線上跑了一段時(shí)間后, 流量都路由到一臺(tái)機(jī)器上了, 導(dǎo)致服務(wù)直接宕機(jī). 大概實(shí)現(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 {
		// 錯(cuò)誤處理
	}
	defer ins.Release()

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

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 其他負(fù)載均衡查了
	}

	// 返回其中一個(gè)ip和port
}

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

修復(fù)方案: 修改成 globalRand 即可.

rand 未來(lái)期望

說(shuō)到這里基本上可以看出來(lái), 為了防止全局鎖競(jìng)爭(zhēng)問(wèn)題, 在使用 math/rand 的時(shí)候, 首先都會(huì)想到自定義 rand, 但是就容易整出來(lái)莫名其妙的問(wèn)題.

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

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

使用 rand.Intn() 確實(shí)會(huì)有全局鎖競(jìng)爭(zhēng)問(wèn)題, 你覺(jué)得 math/rand 未來(lái)會(huì)優(yōu)化嗎? 以及如何優(yōu)化? 歡迎留言討論

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

相關(guān)文章

  • golang雪花算法實(shí)現(xiàn)64位的ID的示例代碼

    golang雪花算法實(shí)現(xiàn)64位的ID的示例代碼

    本文展示了使用Go語(yǔ)言實(shí)現(xiàn)雪花算法生成64位ID的示例代碼,雪花算法通過(guò)當(dāng)前時(shí)間戳、工作節(jié)點(diǎn)ID、數(shù)據(jù)中心ID和序列號(hào)生成唯一的64位ID,確保在分布式系統(tǒng)中的唯一性和時(shí)間順序性,感興趣的可以了解一下
    2024-09-09
  • Golang?urfave/cli庫(kù)簡(jiǎn)單應(yīng)用示例詳解

    Golang?urfave/cli庫(kù)簡(jiǎn)單應(yīng)用示例詳解

    這篇文章主要為大家介紹了Golang?urfave/cli庫(kù)簡(jiǎn)單應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang Gin局部和全局中間件使用詳解

    Golang Gin局部和全局中間件使用詳解

    中間件是放在客戶端和服務(wù)端的中間,當(dāng)你的客戶端對(duì)某個(gè)接口發(fā)起一個(gè)請(qǐng)求,但是在到達(dá)接口2之前,這里是有一層中間件的處理。本文詳細(xì)介紹了Golang Gin局部、全局中間件使用方法,感興趣的同學(xué)可以閱讀本文
    2023-04-04
  • viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽

    viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽

    這篇文章主要介紹了viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Golang中的自定義函數(shù)詳解

    Golang中的自定義函數(shù)詳解

    函數(shù)構(gòu)成代碼執(zhí)行的邏輯結(jié)構(gòu)。在Go語(yǔ)言中,函數(shù)的基本組成為:關(guān)鍵字func、函數(shù)名、參數(shù)列表、返回值、函數(shù)體和返回語(yǔ)句。
    2018-10-10
  • Go web入門Go pongo2模板引擎

    Go web入門Go pongo2模板引擎

    這篇文章主要為大家介紹了Go web編程入門Go pongo2模板引擎使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • mayfly-go部署和使用詳解

    mayfly-go部署和使用詳解

    這篇文章主要介紹了mayfly-go部署和使用詳解,此處部署基于CentOS7.4部署,結(jié)合實(shí)例代碼圖文給大家講解的非常詳細(xì),需要的朋友可以參考下
    2022-09-09
  • GoLang編程必備:GoFrame?GoLand插件介紹

    GoLang編程必備:GoFrame?GoLand插件介紹

    掌握GoLang編程必備工具,我們來(lái)深入了解GoFrame?GoLand插件,這個(gè)小巧但強(qiáng)大的插件將極大提升你的開發(fā)效率,讓代碼流暢如行云流水,一起跟隨本指南,解鎖更智能的編程之旅!
    2023-12-12
  • golang gorm中格式化時(shí)間問(wèn)題詳解

    golang gorm中格式化時(shí)間問(wèn)題詳解

    這篇文章主要給大家介紹了關(guān)于golang gorm中格式化時(shí)間問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用golang具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • GoLang中的iface?和?eface?的區(qū)別解析

    GoLang中的iface?和?eface?的區(qū)別解析

    iface 和 eface 都是 Go 中描述接口的底層結(jié)構(gòu)體,區(qū)別在于 iface 描述的接口包含方法,而 eface 則是不包含任何方法的空接口:interface{},這篇文章主要介紹了GoLang之iface?和?eface?的區(qū)別,需要的朋友可以參考下
    2022-09-09

最新評(píng)論

弥渡县| 海盐县| 大姚县| 临邑县| 清水河县| 阿图什市| 永安市| 长丰县| 保德县| 松阳县| 衡阳市| 怀化市| 彩票| 黑水县| 武义县| 新巴尔虎左旗| 手游| 理塘县| 上杭县| 汉中市| 涞水县| 建始县| 女性| 上思县| 磴口县| 邓州市| 拉萨市| 新密市| 夏津县| 永州市| 仁布县| 伽师县| 唐海县| 朔州市| 镇雄县| 灵石县| 平定县| 汤原县| 获嘉县| 永康市| 潮州市|