澳门百老汇游戏 服务器常用软件

澳门百老汇游戏报道解决Go中使用seed得到相同随机数的问题_澳门百老汇游戏官网资讯

 更新时间:2019年10月09日 08:25:33   作者:detectiveHLH   我要议论
这篇文章主要介绍了Go中使用seed得到相同随机数的问题,需要的朋友能够参考下

1. 反复的随机数

废话不多说,首先我们来看使用seed的一个很奇奥的现象。

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

// 结果如下
// 90
// 90
// 90
// 90
// 90

可能不熟悉seed用法的看到这里会很疑惑,我不是都用了seed吗?英国威廉希尔公司我随机出来的数字都是一样的?不该该每次都纷歧样吗?

可能会有人说是你数据的样本空间太小了,OK,我们加大样本空间到10w再试试。

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

// 结果如下
// 84077
// 84077
// 84077
// 84077
// 84077

你会发现结果依旧是一样的。简单的推理一下我们就能知道,在上面那种情况,每次都取到相同的随机数跟我们所取的样本空间大小是无关的。那么唯一有关的就是seed。我们首先得明确seed的用途。

2. seed的用途

在这里就不卖关子了,先给出结论。

上面每次得到相同随机数是因为在上面的循环中,每次操作的间隔都在毫秒级下,所以每次通过time.Now().Unix()取出来的时间戳都是同一个值,换句话说就是使用了同一个seed。

这个本来很好验证。只需要在每次循环的时刻将生成的时间戳打印出来,你就会发现每次打印出来的时间戳都是一样的。

每次rand都会使用相同的seed来生成随机队列,这样一来在循环中使用相同seed得到的随机队列都是相同的,而生成随机数时每次都会去取同一个身分的数,所以每次取到的随机数都是相同的。

seed 只用于决议一个确定的随机序列。不论seed多大多小,只要随机序列一确定,本身就不会再反复。除非是样本空间太小。解决方案有两种:

在全局初始化挪用一次seed即可
每次使用纳秒级别的种子(强烈不推举这种)

3. 不消每次挪用

上面的解决方案建议各位不要使用第二种,给出是因为在某种情况下的确能够解决问题。比如在你的服务中使用这个seed的地方是串行的,那么每次得到的随机序列的确会纷歧样。

但是如果在高并发下呢?你能够保证每次取到的还是纷歧样的吗?事实证明,在高并发下,即使使用UnixNano作为解决方案,同样会得到相同的时间戳,Go官方也不建议在服务中同时挪用。

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

接下来会带大家了解一下代码的细节。想了解源码的能够继续读下去。

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 { // 如果是负数,则强行转换为一个int32的整数
    seed += int32max
  }
  if seed == 0 { // 如果seed没有被赋值,则默认给一个值
    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赋值了两个定义好的变量,rng.tap和rng.feed。rngLen和rngTap是两个常量。我们来看一下相关的常量定义。

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

由此可见,无论seed是否相同,这两个变量的值都不会受seed的影响。同时,seed的值会最终决议x的值,只要seed相同,则得到的x就相同。并且无论seed是否被赋值,只要检测到是零值,都会默认的赋值为89482311。

接下来我们再看seedrand。

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    // 取除数
  lo := x % Q    // 取余数
  x = A*lo - R*hi // 通过公式重新给x赋值
  if x < 0 {
    x += int32max // 如果x是负数,则强行转换为一个int32的正整数
  }
  return x
}

能够看出,只要传入的x相同,则最后输出的x肯定相同。进而最后得到的随机序列rng.vec就相同。

到此我们验证我们最开头给出的结论,即只要每次传入的seed相同,则生成的随机序列就相同。验证了这个之后我们再继续验证澳门百老汇游戏每次取到的随机序列的值都是相同的。

5. 源码解析-Intn

首先举个例子,来直观的描述上面提到的问题。

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

// 结果
// 81
// 87
// 81
// 87

假设printRandom是一个单独的Go文件,那么你无论run几多次,每次打印出来的随机序列都是一样的。通过阅读seed的源码我们知道,这是因为生成了相同的随机序列。那么澳门百老汇游戏会每次都取到同样的值呢?不说废话,我们一层一层来看。

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,就会直接panic。其次,会依据传入的数据类型,返回对应的类型。

虽然说这里挪用分成了Int31n和Int63n,但是往下看的你会发现,本来都是挪用的r.Int63(),只不外在返回64位的时刻做了一个右移的操作。

// r.Int31n的挪用
func (r *Rand) Int31() int32 { return int32(r.Int63() >> 32) }

// r.Int63n的挪用
func (r *Rand) Int63() int64 { return r.src.Int63() }

5.2 Int63

先给出这个函数的相关代码。

// 返回一个非负的int64伪随机数.
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,最终都会进入Uint64这个函数中。而在这两个函数中,这两个变量的值显得尤为关键。因为直接决议了最后得到的随机数,这两个变量的赋值如下。

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

tap的值是常量0,而feed的值决议于rngLen和rngTap,而这两个变量的值也是一个常量。如此,每次从随机队列中取到的值都是确定的两个值的和。

到这,我们也验证了只要传入的seed相同,并且每次都挪用seed方法,那么每次随机出来的值肯定是相同的。

6. 结论

首先评估是否需要使用seed,其次,使用seed只需要在全局挪用一次即可,如果多次挪用则有可能取到相同随机数。

总结

以上所述是小编给大家介绍的解决Go中使用seed得到相同随机数的问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对澳门百老汇游戏网站的撑腰!
如果你以为本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

  • go
  • seed
  • 随机数

相关文章

  • go语言实现处置表单输入

    go语言实现处置表单输入

    本文给大家分享的是一个使用go语言实现处置表单输入的实例代码,非常的简单,仅仅是实现了用户名暗码的验证,有需要的小伙伴能够自由扩展下。
    2015-03-03
  • Go语言指针访问结构体的方法

    Go语言指针访问结构体的方法

    这篇文章主要介绍了Go语言指针访问结构体的方法,涉及Go语言指针及结构体的使用技术,具有肯定参考借鉴价值,需要的朋友能够参考下
    2015-02-02
  • Go html/template 模板的使用实例详解

    Go html/template 模板的使用实例详解

    这篇文章主要介绍了Go html/template 模板的使用实例详解,小编以为挺不错的,现在分享给大家,也给大家做个参考。一起跟从小编过来看看吧
    2019-05-05
  • 基于Go和Gin的情况配置方法

    基于Go和Gin的情况配置方法

    今天小编就为大家分享一篇基于Go和Gin的情况配置方法,具有很好的参考价值,希望对大家有所帮助。一起跟从小编过来看看吧
    2019-07-07
  • golang中的net/rpc包使用概述(小结)

    golang中的net/rpc包使用概述(小结)

    本篇文章主要介绍了golang中的net/rpc包使用概述(小结),小编以为挺不错的,现在分享给大家,也给大家做个参考。一起跟从小编过来看看吧
    2017-11-11
  • go语言实现一个最简单的http文件服务器实例

    go语言实现一个最简单的http文件服务器实例

    这篇文章主要介绍了go语言实现一个最简单的http文件服务器的方法,实例分析了Go语言操作http的技术,具有肯定参考借鉴价值,需要的朋友能够参考下
    2015-03-03
  • Go语言文件操作的方法

    Go语言文件操作的方法

    这篇文章主要介绍了Go语言文件操作的方法,涉及文件的读写及封闭等操作技术,具有肯定参考借鉴价值,需要的朋友能够参考下
    2015-02-02
  • golang中defer的关键特性示例详解

    golang中defer的关键特性示例详解

    defer是golang语言中的关键字,用于资源的释放,会在函数返回之前进行挪用。下面这篇文章主要给大家介绍了星际娱乐平台怎么样golang中defer的关键特性,文中通过示例代码介绍的非常详细,对大家具有肯定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-08-08
  • 简介Go语言中的select语句的用法

    简介Go语言中的select语句的用法

    这篇文章主要介绍了简介Go语言中的select语句的用法,是golang入门学习中的基础知识,需要的朋友能够参考下
    2015-10-10
  • 详解Go语言RESTful JSON API创建

    详解Go语言RESTful JSON API创建

    这篇文章主要介绍了详解Go语言RESTful JSON API创建,小编以为挺不错的,现在分享给大家,也给大家做个参考。一起跟从小编过来看看吧
    2018-05-05

最新议论

微信 通宝518 投诉建议 在线工具
条评论