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.taprng.feedrngLenrngTap是两个常量。我们来看一下相关的常量定义。

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只需要在全局调用一次即可,如果多次调用则有可能取到相同随机数。

往期文章:

相关:

  • 微信公众号: SH的全栈笔记(或直接在添加公众号界面搜索微信号LunhaoHu)

Go中使用seed得到相同随机数的问题的更多相关文章

  1. c语言中实现从0-1的随机数输出

    原文:c语言中实现从0-1的随机数输出 今天晚上同学问了一个巨简单的问题,问我怎么用c语言输出0-1的随机数,可别说,一时之间还想不出来.在写的过程中发现,直接调用random函数还不能实现,用以下方 ...

  2. 18.翻译系列:EF 6 Code-First 中的Seed Data(种子数据或原始测试数据)【EF 6 Code-First系列】

    原文链接:https://www.entityframeworktutorial.net/code-first/seed-database-in-code-first.aspx EF 6 Code-F ...

  3. Random 中的Seed

    C#中使用随机数 看下例 当Random的种子是0时 生成的随机数列表是一样的 也就是说当seed 一样时 审查的随机数时一样的 Random的无参实例默认 种子 时当前时间 如果要确保生成的随机数不 ...

  4. java中从1000万个随机数中查找出相同的10万个随机数花的最少时间

    偶然在群里看到有人问到大数据查询,自己也就想了小艾改如何解决,从从1000万个随机数中查找出相同的10万个随机数花的最少时间, 谈到效率,自然是hashmap莫属. import java.util. ...

  5. Java中在特定区间产生随机数

    生成指定范围内的随机数 这个是最常用的技术之一.程序员希望通过随机数的方式来处理众多的业务逻辑,测试过程中也希望通过随机数的方式生成包含大量数字的测试用例.问题往往类似于: 如何随机生成 1~100 ...

  6. Java Random、ThreadLocalRandom、UUID类中的方法应用(随机数)

    1.Random:产生一个伪随机数(通过相同的种子,产生的随机数是相同的): Random r=new Random(); System.out.println(r.nextBoolean()); S ...

  7. JAVA中获取不重复的随机数

    我们知道 Random random = new  Random() 中可能会获取到重复的随机数 那么假设要获取1到33之间的六个不重复随机数应该怎么做呢? 首先定义一个数字数组存储1到33 int[ ...

  8. Spring Boot 配置文件中使用变量、使用随机数

    参数引用 在application.properties中的各个参数之间可以直接通过是使用placeHolder的方式进行引用,如: book.author=Clark book.name=C++ b ...

  9. Java开发中经典的小实例-(随机数)

    import java.util.Random;//输出小于33的7个不相同的随机数public class probability {    static Random random = new R ...

随机推荐

  1. Docker笔记(九):网络管理

    Docker的应用运行在容器中,其相互之间或与外部之间是如何通信的,涉及到哪些知识点,本文对相关内容进行整理.因网络这块牵涉的面较多,因此只从日常使用或理解的角度出发,过于专业的就不深入探讨了. 1. ...

  2. lightoj 1283 - Shelving Books(记忆化搜索+区间dp)

    题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1283 题解:这题很显然一看就像是区间dp,但是单纯的区间dp好像解决不了问题可 ...

  3. codeforces 862 C. Mahmoud and Ehab and the xor(构造)

    题目链接:http://codeforces.com/contest/862/problem/C 题解:一道简单的构造题,一般构造题差不多都考自己脑补,脑洞一开就过了 由于数据x只有1e5,但是要求是 ...

  4. 【Redis】安装、开启以及关闭

    一.Linux环境的操作 1.1 下载安装 1.2 启动 1.3 连接Redis客户端 1.4 关闭 二.Windows和Mac下的操作 2.1 下载安装 2.2 启动 2.3 连接客户端 2.4 关 ...

  5. Optional和Stream的map与flatMap

    Optional的map和flatMap Optional存在map和flatMap方法.map源码如下 public<U> Optional<U> map(Function& ...

  6. 056 模块7-os库的基本使用

    目录 一.os库基本介绍 二.os库之路径操作 2.1 路径操作 三.os库之进程管理 3.1 进程管理 四.os库之环境参数 4.1 环境参数 一.os库基本介绍 os库提供通用的.基本的操作系统交 ...

  7. Java EE—最轻量级的企业框架?

    确保高效发展进程的建议 很久以前,J2EE,特别是应用程序服务器被认为过于臃肿和"重量级".对于开发人员来说,使用此技术开发应用程序会非常繁琐且令人沮丧.但是,由于 J2EE 框架 ...

  8. 如何设置java虚拟机参数

    这两天在看java虚拟机,从书上看到可以自己设置java虚拟机的参数,可以方便开发人员进行系统调优和故障排查 Ecplise设置java虚拟机参数: window-->preferences-- ...

  9. Go微服务全链路跟踪详解

    在微服务架构中,调用链是漫长而复杂的,要了解其中的每个环节及其性能,你需要全链路跟踪. 它的原理很简单,你可以在每个请求开始时生成一个唯一的ID,并将其传递到整个调用链. 该ID称为Correlati ...

  10. Linux 笔记 - 第十六章 LNMP 之(一) 环境搭建

    博客地址:http://www.moonxy.com 一.前言 LNMP 中的 N 指 Nginx,在静态页面的处理上,Nginx 较 Apache 更胜一筹:但在动态页面的处理上,Nginx 并不比 ...