乐闻世界logo
搜索文章和话题

Golang 如何地为随机数生成器设置正确的种子?

9 个月前提问
5 个月前修改
浏览次数188

6个答案

1
2
3
4
5
6

在Go语言中,为随机数生成器设置种子通常涉及到math/rand包。这个包提供了伪随机数生成器的功能。rand.Seed函数用来初始化默认的随机数生成器的种子。如果不设置种子,随机数生成器将默认使用种子1,这会导致每次程序运行时生成的随机数序列是相同的。

为了生成不同的随机数序列,我们应该在使用随机数之前提供一个变化的种子。通常,我们会使用当前时间作为种子,因为它总是在变化的。下面是一个如何设置随机数种子的例子:

go
package main import ( "fmt" "math/rand" "time" ) func main() { // 使用当前时间的纳秒时间戳初始化种子 rand.Seed(time.Now().UnixNano()) // 生成一个随机数 randomNumber := rand.Intn(100) // 生成一个[0, 100)的随机数 fmt.Println(randomNumber) }

在这段代码中,我们使用了time.Now().UnixNano()来得到当前时间的纳秒时间戳,并将其作为参数传递给rand.Seed函数。这样,每次程序运行时,由于时间的不同,种子也会不同,从而使得随机数生成器产生不同的随机数序列。

需要注意的是,math/rand包生成的是伪随机数,它们是由确定性算法生成的,因此它们并不适合于所有的场景,尤其是安全性要求较高的场景。对于需要加密安全等级的随机数,应当使用crypto/rand包。

2024年6月29日 12:07 回复

每次设置相同的种子时,您都会得到相同的序列。因此,当然,如果您在快速循环中将种子设置为时间,则可能会使用相同的种子多次调用它。

在您的情况下,当您调用randInt函数直到获得不同的值时,您正在等待时间(由 Nano 返回)更改。

对于所有伪随机库,您只需设置种子一次,例如在初始化程序时,除非您特别需要重现给定的序列(通常仅用于调试和单元测试)。

之后,您只需调用即可Intn获取下一个随机整数。

rand.Seed(time.Now().UTC().UnixNano())代码行从 randInt 函数移至 main 函数的开头,一切都会变得更快。并且.UTC()由于以下原因失去了通话:

UnixNano 返回 t 作为 Unix 时间,即自 UTC 1970 年 1 月 1 日以来经过的纳秒数。

另请注意,我认为您可以简化字符串构建:

shell
package main import ( "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(randomString(10)) } func randomString(l int) string { bytes := make([]byte, l) for i := 0; i < l; i++ { bytes[i] = byte(randInt(65, 90)) } return string(bytes) } func randInt(min int, max int) int { return min + rand.Intn(max-min) }
2024年6月29日 12:07 回复

编辑:这个问题已在 Go 版本 1.20 中得到解决,不再需要自己播种随机源。

我不明白为什么人们要以时间价值播种。根据我的经验,这从来都不是一个好主意。例如,虽然系统时钟可能以纳秒表示,但系统的时钟精度不是纳秒。

该程序不应该在 Go Playground 上运行,但如果您在自己的机器上运行它,您可以粗略估计您可以期望的精度类型。我看到增量约为 1000000 ns,因此增量为 1 毫秒。这是未使用的 20 位熵。_一直以来,高位大多是恒定的!?_一天大约有 24 位熵,这是非常暴力的(可能会产生漏洞)。

这对您来说重要的程度会有所不同,但您可以通过简单地使用作为crypto/rand.Read种子源来避免基于时钟的种子值的陷阱。它将为您提供您可能在随机数中寻找的非确定性质量(即使实际实现本身仅限于一组不同且确定性的随机序列)。

shell
import ( crypto_rand "crypto/rand" "encoding/binary" math_rand "math/rand" ) func init() { var b [8]byte _, err := crypto_rand.Read(b[:]) if err != nil { panic("cannot seed math/rand package with cryptographically secure random number generator") } math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:]))) }

作为旁注,但与您的问题有关。您可以使用此方法创建自己的rand.Source方法,以避免使用锁保护源的成本。包rand实用函数很方便,但它们也在幕后使用锁来防止源被同时使用。如果您不需要,您可以通过创建自己的Source并以非并发方式使用它来避免它。无论如何,您不应该在迭代之间重新播种随机数生成器,它从来没有被设计为以这种方式使用。


编辑:我曾经在 ITAM/SAM 工作,我们构建的客户端(然后)使用了基于时钟的种子。Windows 更新后,公司中的许多机器几乎同时重新启动。这导致了对上游服务器基础设施的 Involtery DoS 攻击,因为客户端使用系统运行时间来播种随机性,而这些机器最终或多或少地随机选择相同的时间段进行报告。它们的目的是在一段时间内涂抹负载。一个小时左右,但那没有发生。种子负责任!

2024年6月29日 12:07 回复

只是为了把它扔给后代:有时最好使用初始字符集字符串生成随机字符串。如果字符串应该由人手动输入,那么这很有用;排除 0、O、1 和 l 有助于减少用户错误。

shell
var alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789" // generates a random string of fixed size func srand(size int) string { buf := make([]byte, size) for i := 0; i < size; i++ { buf[i] = alpha[rand.Intn(len(alpha))] } return string(buf) }

我通常将种子设置在一个init()块内。它们记录在这里:http://golang.org/doc/ effective_go.html #init

2024年6月29日 12:07 回复

在 Go 1.20(2022 年第 4 季度)中,播种随机数生成器的正确方法也可能是……不执行任何操作。

如果Seed未调用,生成器将在程序启动时随机播种。

提案“ math/rand:随机生成全局种子”被接受(2022 年 10 月),并且已经开始实施:

  • CL 443058 : math/rand: 自动种子全局源

实施提案#54880,自动播种全局源。

这不是重大更改的理由是,在包的init函数或导出的 API 中对全局源的任何使用显然都必须有效 - 也就是说,如果包改变了它在某个时间或在导出的 API 中消耗的随机性init,那么显然不是那种需要发布该软件包 v2 的重大更改。
这种按包改变全局源位置与以不同方式播种全局源没有什么区别。因此,如果每个包的更改有效,那么自动播种也是有效的。

当然,自动播种意味着包将不太可能依赖于全局源的特定结果,因此当将来发生此类每个包的更改时也不会中断。

Seed(1)可以在需要全局源旧序列并想要恢复旧行为的程序中调用。
当然,这些程序仍然会被刚才描述的每个包的更改所破坏,并且它们最好分配本地源而不是继续使用全局源。


问题 20661CL 436955中,还请注意math/rand.Read已弃用:对于几乎所有用例,crypto/rand.Read更合适。

正如这里所指出的

可以像这样使用goseclintergolanglint-ci并观察G404代码:

shell
golangci-lint run --disable-all --enable gosec
2024年6月29日 12:07 回复

好吧为什么这么复杂!

shell
package main import ( "fmt" "math/rand" "time" ) func main() { rand.Seed( time.Now().UnixNano()) var bytes int for i:= 0 ; i < 10 ; i++{ bytes = rand.Intn(6)+1 fmt.Println(bytes) } //fmt.Println(time.Now().UnixNano()) }

这是基于dystroy 的代码,但适合我的需要。

这是骰子六(rands ints 1 =< i =< 6

shell
func randomInt (min int , max int ) int { var bytes int bytes = min + rand.Intn(max) return int(bytes) }

上面的函数是完全相同的。

我希望这些信息有用。

2024年6月29日 12:07 回复

你的答案