Go言語のrand.Seed
が非推奨になっていたので備忘録。
はじめに
Go言語のmath/randパッケージにおいて、下記のように乱数のシードを指定すると、deprecatedの警告が出るようになりました。
rand.Seed(time.Now().UnixNano())
ではどうすれば良いのか?の備忘録メモです。
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
の備忘録メモでした。
どなたかの参考になれば幸いです。
[関連記事]
参考
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