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

解決Go中使用seed得到相同隨機(jī)數(shù)的問題

 更新時(shí)間:2019年10月09日 08:25:33   作者:detectiveHLH  
這篇文章主要介紹了Go中使用seed得到相同隨機(jī)數(shù)的問題,需要的朋友可以參考下

1. 重復(fù)的隨機(jī)數(shù)

廢話不多說,首先我們來看使用seed的一個(gè)很神奇的現(xiàn)象。

func main() {
  for i := 0; i < 5; i++ {
  rand.Seed(time.Now().Unix())
    fmt.Println(rand.Intn(100))
  }
}

// 結(jié)果如下
// 90
// 90
// 90
// 90
// 90

可能不熟悉seed用法的看到這里會(huì)很疑惑,我不是都用了seed嗎?為何我隨機(jī)出來的數(shù)字都是一樣的?不應(yīng)該每次都不一樣嗎?

可能會(huì)有人說是你數(shù)據(jù)的樣本空間太小了,OK,我們加大樣本空間到10w再試試。

func main() {
  for i := 0; i < 5; i++ {
  rand.Seed(time.Now().Unix())
    fmt.Println(rand.Intn(100000))
  }
}

// 結(jié)果如下
// 84077
// 84077
// 84077
// 84077
// 84077

你會(huì)發(fā)現(xiàn)結(jié)果仍然是一樣的。簡(jiǎn)單的推理一下我們就能知道,在上面那種情況,每次都取到相同的隨機(jī)數(shù)跟我們所取的樣本空間大小是無關(guān)的。那么唯一有關(guān)的就是seed。我們首先得明確seed的用途。

2. seed的用途

在這里就不賣關(guān)子了,先給出結(jié)論。

上面每次得到相同隨機(jī)數(shù)是因?yàn)樵谏厦娴难h(huán)中,每次操作的間隔都在毫秒級(jí)下,所以每次通過time.Now().Unix()取出來的時(shí)間戳都是同一個(gè)值,換句話說就是使用了同一個(gè)seed。

這個(gè)其實(shí)很好驗(yàn)證。只需要在每次循環(huán)的時(shí)候?qū)⑸傻臅r(shí)間戳打印出來,你就會(huì)發(fā)現(xiàn)每次打印出來的時(shí)間戳都是一樣的。

每次rand都會(huì)使用相同的seed來生成隨機(jī)隊(duì)列,這樣一來在循環(huán)中使用相同seed得到的隨機(jī)隊(duì)列都是相同的,而生成隨機(jī)數(shù)時(shí)每次都會(huì)去取同一個(gè)位置的數(shù),所以每次取到的隨機(jī)數(shù)都是相同的。

seed 只用于決定一個(gè)確定的隨機(jī)序列。不管seed多大多小,只要隨機(jī)序列一確定,本身就不會(huì)再重復(fù)。除非是樣本空間太小。解決方案有兩種:

在全局初始化調(diào)用一次seed即可
每次使用納秒級(jí)別的種子(強(qiáng)烈不推薦這種)

3. 不用每次調(diào)用

上面的解決方案建議各位不要使用第二種,給出是因?yàn)樵谀撤N情況下的確可以解決問題。比如在你的服務(wù)中使用這個(gè)seed的地方是串行的,那么每次得到的隨機(jī)序列的確會(huì)不一樣。

但是如果在高并發(fā)下呢?你能夠保證每次取到的還是不一樣的嗎?事實(shí)證明,在高并發(fā)下,即使使用UnixNano作為解決方案,同樣會(huì)得到相同的時(shí)間戳,Go官方也不建議在服務(wù)中同時(shí)調(diào)用。

Seed should not be called concurrently with any other Rand method.

接下來會(huì)帶大家了解一下代碼的細(xì)節(jié)。想了解源碼的可以繼續(xù)讀下去。

4. 源碼解析-seed

4.1 seed

首先來看一下seed做了什么。

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

  seed = seed % int32max
  if seed < 0 { // 如果是負(fù)數(shù),則強(qiáng)行轉(zhuǎn)換為一個(gè)int32的整數(shù)
    seed += int32max
  }
  if seed == 0 { // 如果seed沒有被賦值,則默認(rèn)給一個(gè)值
    seed = 89482311
  }

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

首先,seed賦值了兩個(gè)定義好的變量,rng.tap和rng.feed。rngLen和rngTap是兩個(gè)常量。我們來看一下相關(guān)的常量定義。

const (
  rngLen  = 607
  rngTap  = 273
  rngMax  = 1 << 63
  rngMask = rngMax - 1
  int32max = (1 << 31) - 1
)

由此可見,無論seed是否相同,這兩個(gè)變量的值都不會(huì)受seed的影響。同時(shí),seed的值會(huì)最終決定x的值,只要seed相同,則得到的x就相同。而且無論seed是否被賦值,只要檢測(cè)到是零值,都會(huì)默認(rèn)的賦值為89482311。

接下來我們?cè)倏磗eedrand。

4.2 seedrand

// seed rng x[n+1] = 48271 * x[n] mod (2**31 - 1)
func seedrand(x int32) int32 {
  const (
    A = 48271
    Q = 44488
    R = 3399
  )

  hi := x / Q    // 取除數(shù)
  lo := x % Q    // 取余數(shù)
  x = A*lo - R*hi // 通過公式重新給x賦值
  if x < 0 {
    x += int32max // 如果x是負(fù)數(shù),則強(qiáng)行轉(zhuǎn)換為一個(gè)int32的正整數(shù)
  }
  return x
}

可以看出,只要傳入的x相同,則最后輸出的x一定相同。進(jìn)而最后得到的隨機(jī)序列rng.vec就相同。

到此我們驗(yàn)證我們最開始給出的結(jié)論,即只要每次傳入的seed相同,則生成的隨機(jī)序列就相同。驗(yàn)證了這個(gè)之后我們?cè)倮^續(xù)驗(yàn)證為什么每次取到的隨機(jī)序列的值都是相同的。

5. 源碼解析-Intn

首先舉個(gè)例子,來直觀的描述上面提到的問題。

func printRandom() {
 for i := 0; i < 2; i++ {
  fmt.Println(rand.Intn(100))
 }
}

// 結(jié)果
// 81
// 87
// 81
// 87

假設(shè)printRandom是一個(gè)單獨(dú)的Go文件,那么你無論run多少次,每次打印出來的隨機(jī)序列都是一樣的。通過閱讀seed的源碼我們知道,這是因?yàn)樯闪讼嗤碾S機(jī)序列。那么為什么會(huì)每次都取到同樣的值呢?不說廢話,我們一層一層來看。

5.1 Intn

func (r *Rand) Intn(n int) int {
  if n <= 0 {
    panic("invalid argument to Intn")
  }
  if n <= 1<<31-1 {
    return int(r.Int31n(int32(n)))
  }
  return int(r.Int63n(int64(n)))
}

可以看到,如果n小于等于0,就會(huì)直接panic。其次,會(huì)根據(jù)傳入的數(shù)據(jù)類型,返回對(duì)應(yīng)的類型。

雖然說這里調(diào)用分成了Int31n和Int63n,但是往下看的你會(huì)發(fā)現(xiàn),其實(shí)都是調(diào)用的r.Int63(),只不過在返回64位的時(shí)候做了一個(gè)右移的操作。

// r.Int31n的調(diào)用
func (r *Rand) Int31() int32 { return int32(r.Int63() >> 32) }

// r.Int63n的調(diào)用
func (r *Rand) Int63() int64 { return r.src.Int63() }

5.2 Int63

先給出這個(gè)函數(shù)的相關(guān)代碼。

// 返回一個(gè)非負(fù)的int64偽隨機(jī)數(shù).
func (rng *rngSource) Int63() int64 {
  return int64(rng.Uint64() & rngMask)
}

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

可以看到,無論是int31還是int63,最終都會(huì)進(jìn)入U(xiǎn)int64這個(gè)函數(shù)中。而在這兩個(gè)函數(shù)中,這兩個(gè)變量的值顯得尤為關(guān)鍵。因?yàn)橹苯記Q定了最后得到的隨機(jī)數(shù),這兩個(gè)變量的賦值如下。

rng.tap = 0
rng.feed = rngLen - rngTap

tap的值是常量0,而feed的值決定于rngLen和rngTap,而這兩個(gè)變量的值也是一個(gè)常量。如此,每次從隨機(jī)隊(duì)列中取到的值都是確定的兩個(gè)值的和。

到這,我們也驗(yàn)證了只要傳入的seed相同,并且每次都調(diào)用seed方法,那么每次隨機(jī)出來的值一定是相同的。

6. 結(jié)論

首先評(píng)估是否需要使用seed,其次,使用seed只需要在全局調(diào)用一次即可,如果多次調(diào)用則有可能取到相同隨機(jī)數(shù)。

總結(jié)

以上所述是小編給大家介紹的解決Go中使用seed得到相同隨機(jī)數(shù)的問題,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!

相關(guān)文章

  • Golang Gob編碼(gob包的使用詳解)

    Golang Gob編碼(gob包的使用詳解)

    這篇文章主要介紹了Golang Gob編碼(gob包的使用詳解),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • 詳解Golang函數(shù)式選項(xiàng)(Functional?Options)模式

    詳解Golang函數(shù)式選項(xiàng)(Functional?Options)模式

    什么是函數(shù)式選項(xiàng)模式,為什么要這么寫,這個(gè)編程模式解決了什么問題呢?其實(shí)就是為了解決動(dòng)態(tài)靈活的配置不同的參數(shù)的問題。下面通過本文給大家介紹Golang函數(shù)式選項(xiàng)(Functional?Options)模式的問題,感興趣的朋友一起看看吧
    2021-12-12
  • Golang中指針的使用詳解

    Golang中指針的使用詳解

    Golang是一門支持指針的編程語(yǔ)言,指針是一種特殊的變量,存儲(chǔ)了其他變量的地址。通過指針,可以在程序中直接訪問和修改變量的值,避免了不必要的內(nèi)存拷貝和傳遞。Golang中的指針具有高效、安全的特點(diǎn),在并發(fā)編程和底層系統(tǒng)開發(fā)中得到廣泛應(yīng)用
    2023-04-04
  • golang判斷文本文件是否是BOM格式的方法詳解

    golang判斷文本文件是否是BOM格式的方法詳解

    在Go語(yǔ)言中,我們可以通過讀取文本文件的前幾個(gè)字節(jié)來識(shí)別它是否是BOM格式的文件,BOM(Byte Order Mark)是UTF編碼標(biāo)準(zhǔn)中的一部分,用于標(biāo)示文本文件的編碼順序,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • go語(yǔ)言 bool類型的使用操作

    go語(yǔ)言 bool類型的使用操作

    這篇文章主要介紹了go語(yǔ)言 bool類型的使用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語(yǔ)言中樂觀鎖與悲觀鎖的具體使用

    Go語(yǔ)言中樂觀鎖與悲觀鎖的具體使用

    樂觀鎖和悲觀鎖是兩種思想,用于解決并發(fā)場(chǎng)景下的數(shù)據(jù)競(jìng)爭(zhēng)問題,本文主要介紹了Go語(yǔ)言中樂觀鎖與悲觀鎖的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • go slice 擴(kuò)容實(shí)現(xiàn)原理源碼解析

    go slice 擴(kuò)容實(shí)現(xiàn)原理源碼解析

    這篇文章主要為大家介紹了go slice 擴(kuò)容實(shí)現(xiàn)原理源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • go語(yǔ)法入門匿名函數(shù)定義及使用示例詳解

    go語(yǔ)法入門匿名函數(shù)定義及使用示例詳解

    這篇文章主要為大家介紹了go語(yǔ)法入門匿名函數(shù)定義及使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 利用Golang實(shí)現(xiàn)對(duì)配置文件加密

    利用Golang實(shí)現(xiàn)對(duì)配置文件加密

    在實(shí)際的應(yīng)用中,配置文件通常包含了一些敏感的信息,如數(shù)據(jù)庫(kù)密碼、API密鑰等,為了保護(hù)這些敏感信息不被惡意獲取,我們可以對(duì)配置文件進(jìn)行加密,本文將介紹如何使用Go語(yǔ)言實(shí)現(xiàn)對(duì)配置文件的加密,需要的朋友可以參考下
    2023-10-10
  • Go并發(fā)控制Channel使用場(chǎng)景分析

    Go并發(fā)控制Channel使用場(chǎng)景分析

    使用channel來控制子協(xié)程的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,缺點(diǎn)是當(dāng)需要大量創(chuàng)建協(xié)程時(shí)就需要有相同數(shù)量的channel,而且對(duì)于子協(xié)程繼續(xù)派生出來的協(xié)程不方便控制
    2021-07-07

最新評(píng)論