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条)

报告相同问题?