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

55 天前
 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)
}
4824 次点击
所在节点    Go 编程语言
56 条回复
afxcn
55 天前
@kneo 确实是几年前遇到的问题,当时解决起来也容易,换种写法就可以了,只是到目前为止还是没搞清楚是因为什么,所以来问了。

说是返回 32 个 0 可能也不是绝对准确的,也可能只是注意到了全是 0 的 token ,因为它最明显。
jiayiming001
55 天前
func main() {
var wait sync.WaitGroup
for i := 0; i < 100; i++ {
wait.Add(1)
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
defer wait.Done()
for j := 0; j < 100; j++ {
helper.RandString(10)
}
}()
}

wait.Wait()
}

运行这个程序,可以看到有的 goroutine 出现了 painc
afxcn
55 天前
@sztink 我也是从网上搜的,印像中 golang random string 它排最前面.
kneo
55 天前
@afxcn 恕我直言,我不认为几年前的问题你今天还能根据记忆还原出真相。除非你现在依然能重现它。
zzhaolei
55 天前
1. 现在的 go1.22.2 版本,并发调用 Intn 并不会 panic ,源码里就没有相关的检查
2. 当 go 协程调用 _seededRand 的时候,程序的运行时已经将 _seededRand 初始化完成,并不存在说你加了 if _seededRand == nil {...init...} 就会好的情况
3. 几年前的问题了,你也不知道当时具体是什么 go 版本,现有的数据不足以用来判断
Ipsum
55 天前
rand.NewSource 记得会有读写过程,你多个协程并发会有 data race
afxcn
55 天前
@zzhaolei 当时猜可能是因为_seededRand 被回收了,所以加了个判断。

因为知道自己是猜的,所以心里不踏实,一段时间后改成用 crypto/rand 。

crypto/rand 倒是没出过问题,只是生成出来的字符串中没有大写的字母,有点奇怪。

@Citrus 说 crypto/rand 性能很差,因为只是生成 token 的时候用到,还未成为优先问题。
yeyypp92
54 天前
rand 不是线程安全的
AceGo
54 天前
@zzhaolei 所有顶层方法都是并发安全的,Source 是并发不安全的
zzhaolei
54 天前
@AceGo 怎么个不安全法?我看源码也没问题啊,没有改 seed ,也没有竞争
MrSeven7
54 天前
亲测并发会 panic ,楼主说的返回 32 个 0 可能是自己的业务代码做了默认值处理吧
zzhaolei
54 天前
@MrSeven7 是怎么 panic 的,可以贴个代码吗
zzhaolei
54 天前
https://go.dev/play/p/GaXBGyvGkEn 我试出来了,确实会 panic ,Source 的 Uint64 方法内部会索引一个定长的数组,并发的情况下可能会出现索引越界的情况

https://imgur.com/QsyRcDU
bv
54 天前
AceGo
54 天前
@zzhaolei rand 中 Source 的注释:
// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
//
// A Source is not safe for concurrent use by multiple goroutines.
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
11 天前

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

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

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

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

© 2021 V2EX