Quantcast
Channel: プログラミング
Viewing all articles
Browse latest Browse all 8395

rand.Seed が deprecated になって変わったこと | Go言語 - BioErrorLog Tech Blog

$
0
0

Go言語のrand.Seedが非推奨になっていたので備忘録。

はじめに

Go言語のmath/randパッケージにおいて、下記のように乱数のシードを指定すると、deprecatedの警告が出るようになりました。

rand.Seed(time.Now().UnixNano())

VSCodeで出たdeprecatedの警告

ではどうすれば良いのか?の備忘録メモです。

rand.Seed が deprecated

いつから

Go 1.20から非推奨になりました。

リリースノートはこちら:
Go 1.20 Release Notes - The Go Programming Language

(本記事執筆時点で既にGo 1.22まで出ているので、だいぶ前からの話ではあります)

変わったこと

  • 自動でランダムなSeedが設定されるようになった
    • つまり、ランダム値を生成するために非固定値(現時刻など)を渡す必要はなくなった
  • 再現性のある乱数を生成するためには、rand.Seedではなくrand.New(rand.NewSource(seed))を使うようになった

まずSeedをコードで指定しない場合には、グローバルな乱数生成器によって自動でseedが設定されます。 なので今後は、ランダムに値を生成したいケースであえてseedを現時刻などで指定する必要はありません。

リリースノートより引用:

The math/rand package now automatically seeds the global random number generator (used by top-level functions like Float64 and Int) with a random value, and the top-level Seed function has been deprecated.

一方、固定値を渡して再現性のある乱数生成をしたい場合は、rand.New(rand.NewSource(seed))を使います。

例:

r := rand.New(rand.NewSource(100))
fmt.Println(r.Uint64())
fmt.Println(r.Uint64())

// 出力結果// 16754280150678289106// 18080215519327371832// ↑この出力結果は何度繰り返しても変わらない

おまけ: ソースコードを読む

せっかくなので、randパッケージのソースコードを読み込んでいこうと思います(versionはgo 1.20.7)。

まずは、seedを指定しないデフォルトの乱数生成を出発点とします。

rand.Uint64()

このUint64()関数の定義をたどると、

// Uint64 returns a pseudo-random 64-bit value as a uint64// from the default Source.func Uint64() uint64 { return globalRand.Uint64() }

globalRandなるものから、Uint64()が呼び出されています。

このglobalRandを追うと、

var globalRand = New(new(lockedSource))

のようにグローバルな変数として定義されています。

ここで呼び出されているNew()は、rand.New(rand.NewSource(100))のようにseedを渡して乱数生成するときに呼び出すrand.New()と同じものですね。

つまり、

  • Seedを指定する場合: rand.NewSource(SEED)をユーザーが渡して乱数生成器が作られる
  • Seedを指定しない場合: rand内部でNew(new(lockedSource))としてグローバル乱数生成器が作られる

という構成が見えてきます。

グローバル乱数生成器を作るNew(new(lockedSource))の中身をもう少し追いかけてみます。

New()Source interfaceを引数としてRandを返すrandパッケージの関数です。

// New returns a new Rand that uses random values from src// to generate other random values.func New(src Source) *Rand {
    s64, _ := src.(Source64)
    return&Rand{src: src, s64: s64}
}

new()はbuiltinパッケージの関数で、型にゼロ値でメモリを割り当てるもの。

// The new built-in function allocates memory. The first argument is a type,// not a value, and the value returned is a pointer to a newly// allocated zero value of that type.funcnew(Type) *Type

そしてlockedSourceは下記のような関数を持つ型です。

type lockedSource struct {
    lk sync.Mutex
    s  *rngSource // nil if not yet allocated
}

//go:linkname fastrand64func fastrand64() uint64var randautoseed = godebug.New("randautoseed")

// source returns r.s, allocating and seeding it if needed.// The caller must have locked r.func (r *lockedSource) source() *rngSource {
    if r.s == nil {
        var seed int64if randautoseed.Value() == "0" {
            seed = 1
        } else {
            seed = int64(fastrand64())
        }
        r.s = newSource(seed)
    }
    return r.s
}

func (r *lockedSource) Int63() (n int64) {
    r.lk.Lock()
    n = r.source().Int63()
    r.lk.Unlock()
    return
}

func (r *lockedSource) Uint64() (n uint64) {
    r.lk.Lock()
    n = r.source().Uint64()
    r.lk.Unlock()
    return
}

func (r *lockedSource) Seed(seed int64) {
    r.lk.Lock()
    r.seed(seed)
    r.lk.Unlock()
}

// seedPos implements Seed for a lockedSource without a race condition.func (r *lockedSource) seedPos(seed int64, readPos *int8) {
    r.lk.Lock()
    r.seed(seed)
    *readPos = 0
    r.lk.Unlock()
}

// seed seeds the underlying source.// The caller must have locked r.lk.func (r *lockedSource) seed(seed int64) {
    if r.s == nil {
        r.s = newSource(seed)
    } else {
        r.s.Seed(seed)
    }
}

// read implements Read for a lockedSource without a race condition.func (r *lockedSource) read(p []byte, readVal *int64, readPos *int8) (n int, err error) {
    r.lk.Lock()
    n, err = read(p, r.source(), readVal, readPos)
    r.lk.Unlock()
    return
}

source()でseedがランダムに生成されている処理などが見れます。

このあたりでやめようと思いますが、もう一つだけ余談。

//go:linkname fastrand64func fastrand64() uint64

go:linknameという書き方を知らなかったので調べてみると、まさにこの部分の実装がGo界隈で問題になっているそうです。

Go界隈で巻き起こった go:linkname 騒動について - ANDPAD Tech Blog

なるほど勉強になります。

おわりに

以上、Go言語のrand.Seedの備忘録メモでした。

どなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

rand package - math/rand - Go Packages

Go 1.20 Release Notes - The Go Programming Language

go - rand.Seed(SEED) is deprecated, how to use NewRand(NewSeed( ) )? - Stack Overflow

GitHub - golang/go at go1.20.7

go/src/math/rand/rand.go at go1.20.7 · golang/go · GitHub

Go界隈で巻き起こった go:linkname 騒動について - ANDPAD Tech Blog


Viewing all articles
Browse latest Browse all 8395

Trending Articles