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

报告相同问题?

悬赏问题

  • ¥15 R语言Rstudio突然无法启动
  • ¥15 关于#matlab#的问题:提取2个图像的变量作为另外一个图像像元的移动量,计算新的位置创建新的图像并提取第二个图像的变量到新的图像
  • ¥15 改算法,照着压缩包里边,参考其他代码封装的格式 写到main函数里
  • ¥15 用windows做服务的同志有吗
  • ¥60 求一个简单的网页(标签-安全|关键词-上传)
  • ¥35 lstm时间序列共享单车预测,loss值优化,参数优化算法
  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图
  • ¥15 写一个方法checkPerson,入参实体类Person,出参布尔值