dsxcv5652 2014-01-04 16:11
浏览 69
已采纳

Go库代码应如何初始化并使用随机数生成?

When writing a Go library that needs to use random numbers, what is the best way to initialize and consume random numbers?

I know that the std way to do this in an application is:

import (
    "math/rand"
    "time"
)

// do the initial seeding in an init fn
func init() {
    // set the global seed and use the global fns
    rand.Seed(time.Now().UTC().UnixNano())
}

func main() {
    fmt.Println(rand.Int())
    fmt.Println(rand.Intn(200))
}

So when I'm writing library code (not in the main package), should I just do the same:

package libfoo

func init() {
    rand.Seed(time.Now().UTC().UnixNano())
}

func AwesomeFoo() {
    r :=  rand.Intn(1000)
    // ...
}

The application using my library might also do its own random number seeding and use rand.Intn, so my question really is - is there any downside to having a library seed the random number generator and some app code (or another library) do so as well?

Also is there any issue with the library using the "global" rand.Intn or rand.Int or should a library create it's own private Rand object via rand.New(src) and use that instead?

I don't have any particular reason for thinking this is unsafe, but I know enough about crypto and PRNGs to know that it is easy to get something wrong if you don't know what you're doing.

For example, here's a simple library for the Knuth (Fisher-Yates) shuffle that needs randomness: https://gist.github.com/quux00/8258425

  • 写回答

2条回答 默认 最新

  • douchen1988 2014-01-04 20:34
    关注

    What's best really just depends on the type of application you're writing and the type of library you want to create. If we're not sure, we can get the most flexibility by using a form of dependency injection through Go interfaces.

    Consider the following naive Monte Carlo integrator that takes advantage of the rand.Source interface:

    package monte
    
    import (
        "math/rand"
    )
    
    const (
        DEFAULT_STEPS = 100000
    )
    
    type Naive struct {
        rand *rand.Rand
        steps int
    }
    
    func NewNaive(source rand.Source) *Naive {
        return &Naive{rand.New(source), DEFAULT_STEPS}
    }
    
    func (m *Naive) SetSteps(steps int) {
        m.steps = steps
    }
    
    func (m *Naive) Integrate1D(fn func(float64) float64, a, b float64) float64 {
        var sum float64
        for i := 0; i < m.steps; i++ {
            x := (b-a) * m.rand.Float64() 
            sum += fn(x)
        }
        return (b-a)*sum/float64(m.steps)
    }
    

    We can then use this package to calculate the value of pi:

    func main() {
        m := monte.NewNaive(rand.NewSource(200))
        pi := 4*m.Integrate1D(func (t float64) float64 {
            return math.Sqrt(1-t*t)
        }, 0, 1)
        fmt.Println(pi)
    }
    

    In this case, the quality of our algorithm's results depend on the type of pseudorandom number generator used, so we need to provide a way for users to swap out one generator for another. Here we've defined an opaque type that takes a random number source in its constructor. By having their random number generator satisfy the rand.Source interface, our application writer can then swap out random number generators as needed.

    However, there are many cases where this is exactly what we don't want to do. Consider a random password or key generator. In that case, what we really want is a high entropy source of truly random data, so we should just use the crypto/rand package internally and hide the details from our application writers:

    package keygen
    
    import (
        "crypto/rand"
        "encoding/base32"
    )
    
    func GenKey() (string, error) {
        b := make([]byte, 20)
        if _, err := rand.Read(b); err != nil {
            return "", err
        }
        enc := base32.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZ346789")
        return enc.EncodeToString(b), nil
    }
    

    Hopefully that helps you make a decision. If the code is for your own applications or applications within a specific company rather than industry wide or public use, lean towards the library design that exposes the fewest internals and creates the fewest dependencies rather than the most general design, since that will ease maintenance and shorten implementation time.

    Basically, if it feels like overkill, it probably is.

    In the case of the Knuth Shuffle, the requirements are simply a decent psuedo-random number generator, so you could simply use an internally seeded rand.Rand object that's private to your package like so:

    package shuffle
    
    import (
        "math/rand"
        "time"
    )
    
    var r *rand.Rand
    
    func init() {
        r = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
    }
    
    func ShuffleStrings(arr []string) {
        last := len(arr)-1
        for i := range arr {
            j := r.Intn(last)
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
    

    Then the application doesn't have to worry about how it works:

    package main
    
    import (
        "shuffle"
        "fmt"
    )
    
    func main() {
        arr := []string{"a","set","of","words"}
        fmt.Printf("Shuffling words: %v
    ", arr)
        for i := 0; i<10; i++ {
            shuffle.ShuffleStrings(arr)
            fmt.Printf("Shuffled words: %v
    ", arr)
        }
    }
    

    This prevents the application from accidentally reseeding the random number generator used by your package by calling rand.Seed.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥30 YOLO检测微调结果p为1
  • ¥20 求快手直播间榜单匿名采集ID用户名简单能学会的
  • ¥15 DS18B20内部ADC模数转换器
  • ¥15 做个有关计算的小程序
  • ¥15 MPI读取tif文件无法正常给各进程分配路径
  • ¥15 如何用MATLAB实现以下三个公式(有相互嵌套)
  • ¥30 关于#算法#的问题:运用EViews第九版本进行一系列计量经济学的时间数列数据回归分析预测问题 求各位帮我解答一下
  • ¥15 setInterval 页面闪烁,怎么解决
  • ¥15 如何让企业微信机器人实现消息汇总整合
  • ¥50 关于#ui#的问题:做yolov8的ui界面出现的问题