侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

解决Go中使用seed得到相同随机数的问题

Go  /  管理员 发布于 5年前   713

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 = 0rng.feed = rngLen - rngTap

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

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

6. 结论

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

总结

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


  • 上一条:
    浅谈Go中数字转换字符串的正确姿势
    下一条:
    Go实现双向链表的示例代码
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2017-09
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-12
    • 2021-01
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-08
    • 2024-11
    • 2025-02
    • 2025-04
    • 2025-05
    • 2025-06
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客