Skip to content

Commit

Permalink
rand: make zero LockedSource useful
Browse files Browse the repository at this point in the history
By changing LockedSource.src from a pointer to a value its zero value
becomes usable without panics.

Document this behavior and add an example. Notably, it is not simply a
Source protected by a sync.Mutex, as Rand also type-asserts on it for
efficiency.

Fixes golang/go#49342

Change-Id: Iec00766957229b7c67ede37894c9e5b9d6d8f852
Reviewed-on: https://go-review.googlesource.com/c/exp/+/385094
Reviewed-by: Rob Pike <[email protected]>
Trust: Cherry Mui <[email protected]>
Run-TryBot: Cherry Mui <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
Merovius authored and robpike committed Feb 18, 2022
1 parent 3a4dbed commit 6cf2b20
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 7 deletions.
29 changes: 29 additions & 0 deletions rand/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,32 @@ func ExampleShuffle_slicesInUnison() {
// B: 2
// C: 3
}

func ExampleLockedSource() {
r := rand.New(new(rand.LockedSource))
r.Seed(42) // Try changing this number!
answers := []string{
"It is certain",
"It is decidedly so",
"Without a doubt",
"Yes definitely",
"You may rely on it",
"As I see it yes",
"Most likely",
"Outlook good",
"Yes",
"Signs point to yes",
"Reply hazy try again",
"Ask again later",
"Better not tell you now",
"Cannot predict now",
"Concentrate and ask again",
"Don't count on it",
"My reply is no",
"My sources say no",
"Outlook not so good",
"Very doubtful",
}
fmt.Println("Magic 8-Ball says:", answers[r.Intn(len(answers))])
// Output: Magic 8-Ball says: Most likely
}
17 changes: 10 additions & 7 deletions rand/rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ func (r *Rand) Shuffle(n int, swap func(i, j int)) {

// Read generates len(p) random bytes and writes them into p. It
// always returns len(p) and a nil error.
// Read should not be called concurrently with any other Rand method.
// Read should not be called concurrently with any other Rand method unless
// the underlying source is a LockedSource.
func (r *Rand) Read(p []byte) (n int, err error) {
if lk, ok := r.src.(*LockedSource); ok {
return lk.Read(p, &r.readVal, &r.readPos)
Expand Down Expand Up @@ -246,10 +247,10 @@ func read(p []byte, src Source, readVal *uint64, readPos *int8) (n int, err erro
* Top-level convenience functions
*/

var globalRand = New(&LockedSource{src: NewSource(1).(*PCGSource)})
var globalRand = New(&LockedSource{src: *NewSource(1).(*PCGSource)})

// Type assert that globalRand's source is a LockedSource whose src is a *rngSource.
var _ *PCGSource = globalRand.src.(*LockedSource).src
// Type assert that globalRand's source is a LockedSource whose src is a PCGSource.
var _ PCGSource = globalRand.src.(*LockedSource).src

// Seed uses the provided seed value to initialize the default Source to a
// deterministic state. If Seed is not called, the generator behaves as
Expand Down Expand Up @@ -335,10 +336,12 @@ func NormFloat64() float64 { return globalRand.NormFloat64() }
func ExpFloat64() float64 { return globalRand.ExpFloat64() }

// LockedSource is an implementation of Source that is concurrency-safe.
// It is just a standard Source with its operations protected by a sync.Mutex.
// A Rand using a LockedSource is safe for concurrent use.
//
// The zero value of LockedSource is valid, but should be seeded before use.
type LockedSource struct {
lk sync.Mutex
src *PCGSource
src PCGSource
}

func (s *LockedSource) Uint64() (n uint64) {
Expand All @@ -365,7 +368,7 @@ func (s *LockedSource) seedPos(seed uint64, readPos *int8) {
// Read implements Read for a LockedSource.
func (s *LockedSource) Read(p []byte, readVal *uint64, readPos *int8) (n int, err error) {
s.lk.Lock()
n, err = read(p, s.src, readVal, readPos)
n, err = read(p, &s.src, readVal, readPos)
s.lk.Unlock()
return
}

0 comments on commit 6cf2b20

Please sign in to comment.