Golang随机数生成器如何正确播种

cop*_*Man 146 random go

我试图在Go中生成一个随机字符串,这是我到目前为止编写的代码:

package main

import (
    "bytes"
    "fmt"
    "math/rand"
    "time"
)

func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}
Run Code Online (Sandbox Code Playgroud)

我的实施很慢.播种使用time在一定时间内带来相同的随机数,因此循环一次又一次地迭代.我该如何改进我的代码?

Den*_*ret 213

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

在你的情况下,当你调用你的randInt函数直到你有不同的值时,你正在等待时间(由Nano返回)来改变.

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

之后,您只需调用Intn以获取下一个随机整数.

rand.Seed(time.Now().UTC().UnixNano())行从randInt函数移动到main的开头,一切都会更快.

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

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UTC().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)
}
Run Code Online (Sandbox Code Playgroud)

  • 您还可以将`rand.Seed(...)`添加到函数`init()`中.在`main()`之前自动调用`init()`.请注意,您不需要从`main()`调用`init()`! (11认同)
  • 请注意,到目前为止,所发布的任何anwers都没有以加密安全的方式初始化种子.根据您的应用程序,这可能无关紧要,或者可能导致灾难性故障. (5认同)
  • @IngoBlechschmidt `math/rand` 无论如何都不是加密安全的。如果这是一个要求,则应使用“crypto/rand”。 (3认同)
  • @Jabba对.我尽可能简单地回答问题,并且离问题不太远,但你的观察是正确的. (2认同)

Joh*_*ren 21

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

该程序不应在Go操场上运行,但是如果您在计算机上运行该程序,则可以粗略估算出可以期望的精度类型。我看到约1000000 ns的增量,所以1毫秒的增量。那是20位未使用的熵。一直以来,高位大多是恒定的。

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

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[:])))
}
Run Code Online (Sandbox Code Playgroud)

作为附带说明,但与您的问题有关。您可以rand.Source使用此方法创建自己的方法,以避免使用锁来保护源代码的开销。所述rand包效用函数是便利的,但它们也使用锁罩下以防止源被同时使用。如果不需要,可以通过创建自己的方法Source并以非并行方式使用它来避免这种情况。无论如何,您不应该在迭代之间重新使用随机数生成器,它永远不会被设计为以这种方式使用。

  • 这个答案很不为人知。特别是对于可能在一秒钟内运行多次的命令行工具,这是必须做的。谢谢 (3认同)

jor*_*lli 16

只是为了后代而抛弃它:有时候最好使用初始字符集字符串生成随机字符串.如果字符串应该由人手动输入,这很有用; 排除0,O,1和l可以帮助减少用户错误.

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)
}
Run Code Online (Sandbox Code Playgroud)

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

  • 据我所知,没有必要在`rand.Intn(len(alpha)-1)`中使用`-1`.这是因为`rand.Intn(n)`总是返回一个小于`n`的数字(换句话说:从零到'n-1`). (9认同)
  • @snap 是正确的;事实上,在 `len(alpha)-1` 中包含 `-1` 可以保证序列中永远不会使用数字 9。 (2认同)
  • 还应该注意,排除0(零)是一个好主意,因为你将字节切片转换为字符串,这导致0成为空字节.例如,尝试在中间创建一个"0"字节的文件,看看会发生什么. (2认同)

Von*_*onC 16

对于 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代码:

golangci-lint run --disable-all --enable gosec
Run Code Online (Sandbox Code Playgroud)


小智 14

好吧为什么这么复杂!

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())
}
Run Code Online (Sandbox Code Playgroud)

这是基于dystroy的代码,但符合我的需要.

它死了六个(rands int 1 =< i =< 6)

func randomInt (min int , max int  ) int {
    var bytes int
    bytes = min + rand.Intn(max)
    return int(bytes)
}
Run Code Online (Sandbox Code Playgroud)

上面的功能完全相同.

我希望这些信息有用.

  • @ThomasModeneis:那是因为他们在操场上[假时间](https://blog.golang.org/playground#TOC_3.1.). (8认同)

小智 10

不确定地为生成器提供种子的最佳方法math/rand是使用hash/maphash( playground ):

package main

import (
    "fmt"
    "hash/maphash"
    "math/rand"
)

func main() {
    r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))
    fmt.Println(r.Int())
}
Run Code Online (Sandbox Code Playgroud)

与 相比time.Now()maphash保证了不同的种子(即使在不同的机器上)。与 相比crypto/rand,它要快得多,并且是一个单行代码。