首页 > 编程语言 >golang随机数源码分析及应用

golang随机数源码分析及应用

时间:2024-02-20 15:25:05浏览次数:25  
标签:rand func golang 源码 int64 随机数 100 seed

引言

大家刚开始使用随机数的时候可能会这样写, 但是他会产生一个问题,这是什么问题呢

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

发现打印出来的结果都是相同的

让我们看看用代码分析为什么产生这个问题

首先golang定义一个长度为607个原素的数组,类型是int64

rand/rng.go文件

var (
   // rngCooked used for seeding. See gen_cooked.go for details.
   rngCooked [607]int64 = [...]int64{1395769623340756751...}
)

通过seed方法给以上的 rngCooked 数组初始化每个元素

// Seed uses the provided seed value to initialize the generator to a deterministic state.
func (rng *rngSource) Seed(seed int64) {
   rng.tap = 0
   rng.feed = rngLen - rngTap  // rngLen和rngTap是常量

   seed = seed % int32max
   if seed < 0 {
      seed += int32max
   }
   if seed == 0 {
      seed = 89482311
   }

   x := int32(seed)
   for i := -20; i < rngLen; i++ { // 为什么要冗余20次,我理解是让数据更加随机,你也可以设置100次,当然数据更随机
      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
      }
   }
}

调用rand.Int31n(100)方法获得100以内的随机数

func (r *Rand) Int31n(n int32) int32 {
   if n <= 0 {
      panic("invalid argument to Int31n")
   }
   if n&(n-1) == 0 { // n is power of two, can mask
      return r.Int31() & (n - 1)
   }
   max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
   v := r.Int31()
   for v > max {
      v = r.Int31()
   }
   return v % n
}

以上就是获得随机数的基本流程,但是似乎并没有解释 循环读取的随机数总是相同的,让我们再来看一段代码

  这个代码运用了典型的 线性同余法(LCG)来获取随机数,线性同余法作为一种伪随机数生成算法,在许多应用场景中基本满足了需求。
// 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
   if x < 0 {
      x += int32max
   }
   return x
}

结论:所以我们可以看出,当X相同的情况下, 生成的随机数序列也是相同的,同一时刻拿到的随机数也就是相同的

安全性

我们在获取随机数的时候是用的排他锁,也就是并发安全的

func (r *lockedSource) Int63() (n int64) {
   r.lk.Lock()
   n = r.source().Int63()
   r.lk.Unlock()
   return
}

非并发安全情况

var (
	rrRand = rand.New(rand.NewSource(time.Now().Unix()))
	rrRand.Intn(100)
	此时并发访问rrRand.Intn(100) 就会出现panic
)
func (r *Rand) Seed(seed int64) {
   if lk, ok := r.src.(*lockedSource); ok {  // 因为rand.NewSource(time.Now().Unix())不是lockedSource,所以不会走这个逻辑
      lk.seedPos(seed, &r.readPos)
      return
   }

   r.src.Seed(seed) // 会走这个逻辑
   r.readPos = 0
}

在什么情况下需要指定不同的seed

比如模拟投掷骰子的实验,每次实验投掷10次骰子,并记录每次投掷的结果。需要运行3次实验,每次实验使用不同的种子值。总之,在业务中,或者在大多数情况下我们不需要指定种子值(go 1.20以后)

应用

  1. 在go 1.20以前,需要在程序启动时手动加上 rand.Seed(time.Now().UnixNano()) 种子值,但是在go 1.20以后就直接使用rand.IntN(100) 获取随机数就可以了 ,如下代码
func (r *lockedSource) source() *rngSource {
   if r.s == nil {
      var seed int64
      if randautoseed.Value() == "0" {
         seed = 1 // 环境变量为0,seed默认1,每次获得的随机数序列是一样的
      } else {
         seed = int64(fastrand64())
      }
      r.s = newSource(seed)
   }
   return r.s
}
  1. 如果需要特定的随机序列 请单独使用 rrRand = rand.New(rand.NewSource(time.Now().UnixNano())),这样就不会影响其他使用方使用全局随机数,但是请记住这个不是并发安全的,在并发的情况下需要加排他锁
  2. math.rand包产生的随机数是伪随机数,也就是会存在周期性重复,

对于公式 RandSeed = (A * RandSeed + B) % M 要达到周期重复需满足以下条件

  • B,M互质;
  • M的所有质因数都能整除A-1;
  • 若M是4的倍数,A-1也是;
  • A,B,N[0]都比M小;
  • A,B是正整数。
  1. 如果需要真正意义的随机数,请使用encrypt/rand包 ,由于crypto/rand包使用的是基于密码学的安全随机数生成器(CSPRNG),因此生成的随机数序列是不可预测的、不可重复的。这意味着,即使使用相同的种子值,每次生成的随机数序列也应该是不同的。

标签:rand,func,golang,源码,int64,随机数,100,seed
From: https://www.cnblogs.com/agopher/p/18023168

相关文章

  • golang数组&切片&map
    数组数组声明funcmain(){ /*语法一*///数组名字[数组长度]数组类型 //声明一个数组长度为3类型是int会初始化为int类型的零值,默认值是[000] //声明数组的时候指定长度是一个常量,数组的不可改变,超出长度会报错 vararr[3]int //数组赋值 arr[0]=1......
  • Sentinel 源码学习
    引入依赖<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>1.8.7</version></dependency>基本用法try(Entryentry=SphU.entry("HelloWorld")){......
  • 零基础教你搭建自己的chatgpt,结合midjourney,部署源码上线即可运营
    人工智能日益发展的今天,拥有一套属于自己的ai系统已经不再是遥不可及的梦想。松鼠AI汇集了chatgpt、midjourney、gpt绘画和gpt语音等众多功能,使得它拥有了包括思维导图生成、PPT自动创作、多样化的登录选项以及个性化分销机制在内的过二十种高级功能。下面介绍如何从零开始,搭建......
  • RocketMQ源码系列(1) — 基础架构
    基础架构我们先通过下面这张图来整体的对RocketMQ有一个基础架构上的认识。RocketMQ主要有四个角色:NameServer、Broker、Producer、Consumer。Broker每台机器上部署的RocketMQ进程一般称之为Broker,生产者向Broker发送消息,Broker收到生产者的消息后存储到本地磁盘文件中......
  • 基于Java+SpringBoot+vue的采购管理系统(源码及功能分析)
    前言:随着全球化和信息化的发展,企业采购管理面临越来越多的挑战。传统的采购方式往往涉及到多个繁琐的步骤,包括供应商筛选、询价、招投标等,这些过程不仅耗时,而且容易出错。为了解决这些问题,供应商、询价、招投标一体化系统应运而生。该系统通过集成供应商管理、询价管理、招投标......
  • springboot整合activiti工作流(源码及功能分析)
    前言activiti工作流引擎项目,企业erp、oa、hr、crm等企事业办公系统轻松落地,一套完整并且实际运用在多套项目中的案例,满足日常业务流程审批需求。一、项目形式springboot+vue+activiti集成了activiti在线编辑器,流行的前后端分离部署开发模式,快速开发平台,可插拔工作流服务。工作......
  • JUC并发编程与源码分析
    基础JUC是java.util.concurrent在并发编程中使用的工具包。线程的start()方法底层使用本地方法start0()调用C语言接口,再由C语言接口调用操作系统创建线程。publicclassdemo(){publicstaticvoidmain(Strings[]args){Threadt1=newThread(()->{System......
  • golang运算符&流程控制
    运算符算数运算funcmain(){ varaint=10 varbint=8 varc=3.14 vard=5.15 fmt.Println(a+b)//18 fmt.Println(a-b)//2 fmt.Println(a*b)//80 fmt.Println(a/b)//1,Go中,如果运算的都是整数,相除后会默认去掉小数,保留整数部分, fmt.Pr......
  • springMvc源码解析
    流程:    》DispatcherServlet:前端控制器 》HandlerMapping:处理器映射器主要是为了找到处理器执行链,执行链中包含有实际的处理类、拦截器   》HandlerAdapter:处理器适配器主要是根据上一步的handle,适配选择对应的适配器。 》Handler(处理......
  • linux 中生成随机数
     001、$RANDOM(0~32767的随机数)a、[root@pc1test1]#echo$RANDOM##直接输出31128[root@pc1test1]#echo$RANDOM2539[root@pc1test1]#echo$RANDOM23307 b、限定范围[root@pc1test1]#echo$(($RANDOM%100))##生成0-99的随机数26[root@......