mrand: implement NewSyncRand and use that for DefaultRand, to make it thread-safe

This commit is contained in:
Brian Picciano 2019-07-29 23:24:04 -04:00
parent 0e64f16f03
commit df01ccffcb
2 changed files with 78 additions and 1 deletions

67
mrand/lockedSource.go Normal file
View File

@ -0,0 +1,67 @@
package mrand
import (
"math/rand"
"sync"
)
// Everything in this file is taken from the math/rand package, which really
// ought to expose lockedSource publicly.
func read(p []byte, int63 func() int64, readVal *int64, readPos *int8) (n int, err error) {
pos := *readPos
val := *readVal
for n = 0; n < len(p); n++ {
if pos == 0 {
val = int63()
pos = 7
}
p[n] = byte(val)
val >>= 8
pos--
}
*readPos = pos
*readVal = val
return
}
type lockedSource struct {
lk sync.Mutex
src rand.Source64
}
func (r *lockedSource) Int63() (n int64) {
r.lk.Lock()
n = r.src.Int63()
r.lk.Unlock()
return
}
func (r *lockedSource) Uint64() (n uint64) {
r.lk.Lock()
n = r.src.Uint64()
r.lk.Unlock()
return
}
func (r *lockedSource) Seed(seed int64) {
r.lk.Lock()
r.src.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.src.Seed(seed)
*readPos = 0
r.lk.Unlock()
}
// 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.src.Int63, readVal, readPos)
r.lk.Unlock()
return
}

View File

@ -14,6 +14,16 @@ type Rand struct {
*rand.Rand *rand.Rand
} }
// NewSyncRand initializes and returns a new Rand instance using the given
// Source. The returned Rand will be safe for concurrent use.
//
// This will panic if the given Source doesn't implement rand.Source64.
func NewSyncRand(src rand.Source) Rand {
return Rand{
Rand: rand.New(&lockedSource{src: src.(rand.Source64)}),
}
}
// Bytes returns n random bytes. // Bytes returns n random bytes.
func (r Rand) Bytes(n int) []byte { func (r Rand) Bytes(n int) []byte {
b := make([]byte, n) b := make([]byte, n)
@ -67,7 +77,7 @@ func (r Rand) Element(slice interface{}, weight func(i int) uint64) interface{}
// DefaultRand is an instance off Rand whose methods are directly exported by // DefaultRand is an instance off Rand whose methods are directly exported by
// this package for convenience. // this package for convenience.
var DefaultRand = Rand{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))} var DefaultRand = NewSyncRand(rand.NewSource(time.Now().UnixNano()))
// Methods off DefaultRand exported to the top level of this package. // Methods off DefaultRand exported to the top level of this package.
var ( var (