Go Rand is a feature often used in development, but there are some pitfalls in it. This article will completely break down Go math/rand and make it easy for you to use Go Rand.
First of all, a question: Do you think rand will panic ?
Source Code Analysis
The math/rand source code is actually quite simple, with just two important functions.
|
|
This function is setting the seed, which is actually setting the value of each position of rng.vec. The size of rng.vec is 607.
This is the function we end up calling when we use other functions like Intn(), Int31n(), etc. You can see that each call is the result of adding two values from rng.vec using the rng.feed rng.tap. The result is also put back into rng.vec.
Note here that when using rngSource with rng.go, since rng.vec sets the value of rng.vec at the same time as the random number, there will be data competition when multiple goroutines are called at the same time. math/rand solves this by locking sync.Mutex when calling rngSource.
We can also use rand.Seed()
, rand.Intn(100)
directly, because math/rand initializes a globalRand variable.
Note that since the call to rngSource is locked, using rand.Int32()
directly will result in global goroutine lock contention, so in high concurrency scenarios, when your program’s performance is stuck here, you need to consider using New(&lockedSource{src: NewSource(1). (*rngSource)})
to generate separate rands for different modules. However, based on current practice, using globalRand locks is not as competitive as we might think. There is a pitfall in using New to generate a new rand, which is how the panic in the opening post was created, as we will see later.
Seed What exactly is the role of seeds?
Results:
This example leads to the conclusion that the same seed will give the same result every time it is run. Why is that?
When you use math/rand, you must set the seed by calling rand.Seed, which actually sets the corresponding value for the 607 slots in rng.vec. Seed will call a seedrand function to calculate the value of the corresponding slot.
The results of this function are not random, but are actually calculated according to seed. In addition, this function is not just written, but has a mathematical proof.
This means that the same seed is set to the same value in rng.vec, and the same value is retrieved by Intn.
The traps I encountered
1. rand panic
The screenshot at the beginning of the article is a panic that occurred one day during the development of the project using the underlying library wrapped by someone else. The approximate code implementation is as follows.
This Random will be called concurrently, and since rrRand is not concurrently safe, it will occasionally panic when calling rrRand.
When using math/rand, some people use math.Intn() to initialize a new rand with rand.New, because they are worried about lock competition, but note that the rand initialized by rand.New is not concurrency-safe.
The fix: replace rrRand with globalRand, which has little effect on global locking in online high concurrency scenarios.
2. always load to the same machine
It is also an underlying rpc library that uses random traffic distribution, and after running online for a while, the traffic is routed to one machine, causing the service to go down. The approximate implementation code is as follows.
|
|
The reason for the problem: It can be seen that each request comes with an ip and port using GetInstance, and if we use the Random method of traffic load balancing, each time we reinitialize a rand. We already know that when the same seed is set, the result is the same for each run. When the instant traffic is too large, the concurrent request GetInstance, because the value of time.Now().Unix() is the same at that moment, this will result in getting the same random number, so the last ip, port are the same, the traffic is distributed to this machine.
Fix: Change it to globalRand.
rand future expectations
Basically, you can see that in order to prevent global lock competition, when using math/rand, the first thing that comes to mind is custom rand, but it’s easy to get into some kind of trouble.
Why does math/rand need to be locked?
We all know that math/rand is pseudo-random, but after setting the seed, the value of rng.vec array is basically determined, which is obviously not random anymore. .vec, so it is necessary to lock rng.vec to protect it.
Using rand.Intn() does have a global lock competition problem, I wonder if math/rand will be optimized in the future.