一文完全掌握 Go math/rand(源码解析)
对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《一文完全掌握 Go math/rand(源码解析)》,主要介绍了rand、math,希望对大家的知识积累有所帮助,快点收藏起来吧,否则需要时就找不到了!
Go 获取随机数是开发中经常会用到的功能, 不过这个里面还是有一些坑存在的, 本文将完全剖析 Go math/rand, 让你轻松使用 Go Rand.
开篇一问: 你觉得 rand 会 panic 吗 ?
源码剖析
math/rand 源码其实很简单, 就两个比较重要的函数
func (rng *rngSource) Seed(seed int64) { rng.tap = 0 rng.feed = rngLen - rngTap //... x := int32(seed) for i := -20; i = 0 { var u int64 u = int64(x) <p>这个函数就是在设置 seed, 其实就是对 rng.vec 各个位置设置对应的值. rng.vec 的大小是 607.</p> <pre class="brush:java;"> func (rng *rngSource) Uint64() uint64 { rng.tap-- if rng.tap <p>我们在使用不管调用 Intn(), Int31n() 等其他函数, 最终调用到就是这个函数. 可以看到每次调用就是利用 rng.feed rng.tap 从 rng.vec 中取到两个值相加的结果返回了. 同时还是这个结果又重新放入 rng.vec.</p> <p>在这里需要注意使用 rng.go 的 rngSource 时, 由于 rng.vec 在获取随机数时会同时设置 rng.vec 的值, 当多 goroutine 同时调用时就会有数据竞争问题. math/rand 采用在调用 rngSource 时加锁 sync.Mutex 解决.</p> <pre class="brush:java;"> func (r *lockedSource) Uint64() (n uint64) { r.lk.Lock() n = r.src.Uint64() r.lk.Unlock() return }
另外我们能直接使用 rand.Seed()
, rand.Intn(100)
, 是因为 math/rand 初始化了一个全局的 globalRand 变量.
var globalRand = New(&lockedSource{src: NewSource(1).(*rngSource)}) func Seed(seed int64) { globalRand.Seed(seed) } func Uint32() uint32 { return globalRand.Uint32() }
需要注意到由于调用 rngSource 加了锁, 所以直接使用 rand.Int32()
会导致全局的 goroutine 锁竞争, 所以在高并发场景时, 当你的程序的性能是卡在这里的话, 你需要考虑利用 New(&lockedSource{src: NewSource(1).(*rngSource)})
为不同的模块生成单独的 rand. 不过根据目前的实践来看, 使用全局的 globalRand 锁竞争并没有我们想象中那么激烈. 使用 New 生成新的 rand 里面是有坑的, 开篇的 panic 就是这么产生的, 后面具体再说.
种子(seed)到底起什么作用 ?
func main() { for i := 0; i <p>结果:</p> <blockquote> <p>current:1613814632<br> 65<br> current:1613814632<br> 65<br> current:1613814632<br> 65<br> ...</p> </blockquote> <p>这个例子能得出一个结论: 相同种子,每次运行的结果都是一样的. 这是为什么呢?</p> <p>在使用 math/rand 的时候, 一定需要通过调用 rand.Seed 来设置种子, 其实就是给 rng.vec 的 607 个槽设置对应的值. 通过上面的源码那可以看出来, rand.Seed 会调用一个 seedrand 的函数, 来计算对应槽的值.</p> <pre class="brush:plain;"> 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 <p>这个函数的计算结果并不是随机的, 而是根据 seed 实际算出来的. 另外这个函数并不是随便写的, 是有相关的数学证明的.</p> <p>这也导致了相同的 seed, 最终设置到 rng.vec里面的值是相同的, 通过 Intn 取出的也是相同的值</p> <h2>我遇到的那些坑</h2> <p>1. rand panic</p> <p>文章开头的截图就是项目开发中使用别人封装的底层库, 在某天出现的 panic. 大概实现的代码</p> <pre class="brush:plain;"> // random.go var ( rrRand = rand.New(rand.NewSource(time.Now().Unix())) ) type Random struct{} func (r *Random) Balance(sf *service.Service) ([]string, error) { // .. 通过服务发现获取到一堆ip+port, 然后随机拿到其中的一些ip和port出来 randIndexes := rrRand.Perm(randMax) // 返回这些ip 和port }
这个 Random 会被并发调用, 由于 rrRand 不是并发安全的, 所以就导致了调用 rrRand.Perm 时偶尔会出现 panic 情况.
在使用 math/rand 的时候, 有些人使用 math.Intn() 看了下注释发现是全局共享了一个锁, 担心出现锁竞争, 所以用 rand.New 来初始化一个新的 rand, 但是要注意到 rand.New 初始化出来的 rand 并不是并发安全的.
修复方案: 就是把 rrRand 换成了 globalRand, 在线上高并发场景下, 发现全局锁影响并不大.
2. 获取的都是同一个机器
同样也是底层封装的 rpc 库, 使用 random 的方式来流量分发, 在线上跑了一段时间后, 流量都路由到一台机器上了, 导致服务直接宕机. 大概实现代码:
func Call(ctx *gin.Context, method string, service string, data map[string]interface{}) (buf []byte, err error) { ins, err := ral.GetInstance(ctx, ral.TYPE_HTTP, service) if err != nil { // 错误处理 } defer ins.Release() if b, e := ins.Request(ctx, method, data, head); e == nil { // 错误处理 } // 其他逻辑, 重试等等 } func GetInstance(ctx *gin.Context, modType string, name string) (*Instance, error) { // 其他逻辑.. switch res.Strategy { case WITH_RANDOM: if res.rand == nil { res.rand = rand.New(rand.NewSource(time.Now().Unix())) } which = res.rand.Intn(res.count) case 其他负载均衡查了 } // 返回其中一个ip和port }
引起问题的原因: 可以看出来每次请求到来都是利用 GetInstance 来获取一个 ip 和 port, 如果采用 Random 方式的流量负载均衡, 每次都是重新初始化一个 rand. 我们已经知道当设置相同的种子,每次运行的结果都是一样的. 当瞬间流量过大时, 并发请求 GetInstance, 由于那一刻 time.Now().Unix() 的值是一样的, 这样就会导致获取到随机数都是一样的, 所以就导致最后获取到的 ip, port都是一样的, 流量都分发到这台机器上了.
修复方案: 修改成 globalRand 即可.
rand 未来期望
说到这里基本上可以看出来, 为了防止全局锁竞争问题, 在使用 math/rand 的时候, 首先都会想到自定义 rand, 但是就容易整出来莫名其妙的问题.
为什么 math/rand 需要加锁呢?
大家都知道 math/rand 是伪随机的, 但是在设置完 seed 后, rng.vec 数组的值基本上就确定下来了, 这明显就不是随机了, 为了增加随机性, 通过 Uint64() 获取到随机数后, 还会重新去设置 rng.vec. 由于存在并发获取随机数的需求, 也就有了并发设置 rng.vec 的值, 所以需要对 rng.vec 加锁保护.
使用 rand.Intn() 确实会有全局锁竞争问题, 你觉得 math/rand 未来会优化吗? 以及如何优化? 欢迎留言讨论
以上就是《一文完全掌握 Go math/rand(源码解析)》的详细内容,更多关于golang的资料请关注golang学习网公众号!

- 上一篇
- gorm update传入struct对象,零值字段不更新的解决方案

- 下一篇
- go实现for range迭代时修改值的操作
-
- 精明的板凳
- 赞 👍👍,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢up主分享技术文章!
- 2023-02-24 07:58:06
-
- 靓丽的麦片
- 这篇博文太及时了,很详细,很棒,码起来,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-02-11 20:04:12
-
- 愉快的外套
- 太全面了,已加入收藏夹了,感谢老哥的这篇技术文章,我会继续支持!
- 2023-01-31 01:44:48
-
- 痴情的雪碧
- 这篇博文真是及时雨啊,细节满满,很好,收藏了,关注师傅了!希望师傅能多写Golang相关的文章。
- 2023-01-24 07:03:49
-
- 风中的往事
- 很好,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢作者分享文章!
- 2023-01-07 14:58:46
-
- 心灵美的荷花
- 这篇技术贴太及时了,细节满满,受益颇多,已收藏,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-01-05 06:12:49
-
- 淡然的蜜粉
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢楼主分享技术文章!
- 2023-01-04 13:16:07
-
- 敏感的手套
- 细节满满,收藏了,感谢老哥的这篇技术文章,我会继续支持!
- 2023-01-03 09:03:55
-
- Golang · Go教程 | 16分钟前 |
- Golang并发控制:Mutex与RWMutex使用详解
- 430浏览 收藏
-
- Golang · Go教程 | 17分钟前 |
- Golang并发安全验证方法解析
- 103浏览 收藏
-
- Golang · Go教程 | 18分钟前 | golang 高性能 sync.Mutex 文件并发写入 os.OpenFile
- Golang高并发文件写入优化技巧
- 186浏览 收藏
-
- Golang · Go教程 | 19分钟前 |
- 降低GolangGC停顿,优化内存分配策略
- 207浏览 收藏
-
- Golang · Go教程 | 21分钟前 |
- Golang类型别名与定义的区别详解
- 302浏览 收藏
-
- Golang · Go教程 | 24分钟前 |
- Golang对象池与缓存实战教程
- 275浏览 收藏
-
- Golang · Go教程 | 32分钟前 |
- Golang空接口与反射应用详解
- 374浏览 收藏
-
- Golang · Go教程 | 34分钟前 |
- Golang指针类型有哪些?map/channel/function解析
- 376浏览 收藏
-
- Golang · Go教程 | 34分钟前 |
- Java并发实现方式详解
- 218浏览 收藏
-
- Golang · Go教程 | 37分钟前 |
- Golang字节切片操作技巧分享
- 243浏览 收藏
-
- Golang · Go教程 | 38分钟前 |
- Golang接口隔离:实现清晰领域边界
- 136浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 边界AI平台
- 探索AI边界平台,领先的智能AI对话、写作与画图生成工具。高效便捷,满足多样化需求。立即体验!
- 419次使用
-
- 免费AI认证证书
- 科大讯飞AI大学堂推出免费大模型工程师认证,助力您掌握AI技能,提升职场竞争力。体系化学习,实战项目,权威认证,助您成为企业级大模型应用人才。
- 426次使用
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 561次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 665次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 570次使用
-
- golang中随机数rand的使用
- 2023-01-27 494浏览