使用 go 遇到的一个奇怪问题,求教

236 天前
 afxcn

下面这段代码在长时间运行后,有一定的机率会出错,RandString(32)返回的全是 0.

从网上查的资料全局变量应该不会被回收才对。

package helper

import (
	"math/rand"
	"time"
)

const _charsetRand = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$"

var _seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))

// RandStringWithCharset rand string with charset
func RandStringWithCharset(length int, charset string) string {
	b := make([]byte, length)
	l := len(charset)
	for i := range b {
		b[i] = charset[_seededRand.Intn(l)]
	}
	return string(b)
}

// RandString rand string
func RandString(length int) string {
	return RandStringWithCharset(length, _charsetRand)
}

// RandInt rand int between [min, max)
func RandInt(min int, max int) int {
	if min <= 0 || max <= 0 {
		return 0
	}

	if min >= max {
		return max
	}

	return _seededRand.Intn(max-min) + min
}

// RandMax rand int between [0, max)
func RandMax(max int) int {
	if max <= 1 {
		return 0
	}

	return _seededRand.Intn(max)
}
5092 次点击
所在节点    Go 编程语言
56 条回复
yianing
236 天前
@ixiaohei 看了下全局用的是 lockedSource ,new 出来的是 rngSource ,使用确实要加锁
cloudzhou
236 天前
https://pkg.go.dev/math/rand#NewSource

NewSource returns a new pseudo-random Source seeded with the given value. Unlike the default Source used by top-level functions, this source is not safe for concurrent use by multiple goroutines. The returned Source implements Source64.
afxcn
236 天前
@hxzhouh1 很难重现了,21 年的事情,应该是当时最新版本的 go ,但我当时在自己的电脑跑,都没办法重现。

只有在测试服务器上出现,而且不是一次;第一次出现的时候以为是服务器被黑了,后来才定位到这段代码。
cloudzhou
236 天前
/*
* Top-level convenience functions
*/

var globalRand = New(&lockedSource{src: NewSource(1).(Source64)})
zhaoxueqin248
236 天前
@afxcn 这个 math/rand 的 IntN 在搞并发下运行会有概率 panic 的, 是不是上层有 recover 默认值 导致的 0 ?
rockyliang
236 天前
使用`go run -race`命令检测这段代码,确实是存在竞态并发安全问题,具体是在`b[i] = charset[_seededRand.Intn(l)]`这一行代码处
sztink
236 天前
靠,你这生成随机字符代码难怪看着眼熟,我前段时候从网络上 copy 的一份跟你的几乎一模一样,我得赶紧 fix 一下。copy 来源: https://github.com/BelphegorPrime/lib/blob/master/RandString.go
hopingtop
236 天前
这里能展示一下, 真正写入 全是 0 ,上层的函数吗?
比如这个函数上面有 recover , 这个时候你们得到的 值可能是 空字符串, 但是在你们存入的时候, 这个空字符串经过一些 编码,比如 hex 这种, 就会变成 0000..000 但是是 64 位, 假设你存入的 varchar(32) 在不经过 mysql 严格模式下, 就能把 空字符串 变成 32 位 000... 然后误解,是这里生成的了。

当前上面也只是我的一种 推理可能性
0o0O0o0O0o
236 天前
-race
NoobPhper
236 天前
想象一下同一 ns 情况下 两个 routine 调用了 你哪个不安全的 var , 得到的结果
Karte
236 天前
@dododada @Livid AI 回答.
nuk
236 天前
遇到过差不多的问题,不过因为代码不多,很快就发现是 race condition
https://imgur.com/dp2lnEy
hopingtop
236 天前
@hopingtop #28 现在肯定能够确认这段代码非线程安全的,会有数据竞争,出现 Panic
但是就是特别想知道为啥是 32 个 0 看是否是我上面描述的猜测
AceGo
236 天前
@afxcn #2 NewSource 是线程不安全的,在并发下会 panic 。
这两种写法都用到 NewSource ,应该会报一样的错误
AceGo
236 天前
@yianing #19 Intn 方法会最终调用到 Source 的 Int 方法,所以最终是线程不安全的
mightybruce
236 天前
回答竟然都是竞态并发安全问题, 你们真的是认真的吗
sztink
236 天前
@afxcn 这样是解决不了本质问题的。因为 rand.NewSource 不是并发安全的。另外 createRand 中_seededRand 初始化的逻辑也不是并发安全的。你应该直接用全局函数 rand.Intn()。
cloudzhou
236 天前
@mightybruce 这就是最大可能阿,难道不是并发问题?起码代码就不是并发安全
你是认真的吗?
kuanat
236 天前
对于 rand.NewSource 存在竞态导致 panic 这一点应该没有异议,而 panic 没有中断而是正常被调用,说明上层应该是存在 recover 逻辑的。结合 OP #23 指出本地正常而测试服务器异常,推测本地测试的生成逻辑,而服务器完成的是全部调用逻辑。

@R18 #16 指出 byte 空值转换为 string 之后是空字符串,说明在可见代码的部分,只可能产生空字符串。 @AceGo 和 @hopingtop 判断出是上层调用将空字符串变成了全 0 字符串。

我这里做一点补充,一般要保证字符串定长都会做 padding ,用字符串 0 做填充是最常见的。
kneo
236 天前
你的意思是现在重现不了了,2021 年能重现?是我理解错了吗?几年前的事情你现在才来问?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1038255

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX