前些日子, 收到一個小需求需要隨機產生一組帶大小寫字母和數字的亂數字串, 想說需求滿簡單的, 快速寫一下就寫完commit了, 然後過不久就爆掉了.
來看看究竟寫了些什麼鬼東西~
1 | public class RandomUtil |
程式內容很單純, 就是用System.Random
隨機生出指定長度的字串, 但問題就出在需要隨機產生兩組, 於是呼叫端就呼叫了兩次, 像是這樣:
1 | var rdm = new RandomUtil(); |
然後產生的兩個結果字串一模一樣, Why?
同時建立多個Random
Random
的產生方式是基於一個種子來產生的, 也就是public Random(int Seed)
中的Seed
, 也就是說如果種子一樣, 那兩個new出來的Random物件產生的隨機數是一模一樣的, 而另外一個不帶參數的建構子呢?
從referencesource.microsoft.com上面可以查到原始碼是這樣的:
1 | public Random() |
是的,預設以Environment.TickCount
做為種子, 而Environment.TickCount
是衍生自系統計時器的一個值.
因為
Random()
的亂數是基於系統計時器產生的, 所以如果在極短時間內 (Environment.TickCount相同) 建立多個Random
實例,就會導致產生的亂數是一樣的.
知道問題後, 腦中閃過兩個做法:
解一: Thread.Sleep()
在new Random()
之前, 先延時一毫秒, 避免拿到重複的時間, 雖然直覺但我個人不喜歡.
解二: 建立唯一的Random
1 | public class RandomUtil |
RandomUtil
初始化的時候就建立一個唯一的Random
物件, 避免短時間內重複建立, 呼叫端程式碼不變, 這次兩次產生的結果是一樣的了,問題在大部分的情境下解決了.
多執行緒環境下的Random
考慮同時有兩條執行緒都建立了Random
, 簡單比對一下結果:
1 | string firstThreadResult = null; |
實驗結果顯示, 兩個產生的字串一樣, 所以在多執行緒的情境下, 還是有機會產生重複的亂數組合.
解三: RNGCryptoServiceProvider
1 | public class RandomUtil |
RNGCryptoServiceProvider
可以避免Random
在多執行緒情境下的重複問題, 但缺點就是他不像Random
提供那麼多方法, 所以需要自己實作Next()
,Next(max)
,Next(min, max)
等方法.
後來我把相關方法整理重構過放在我的 Github 上了, 實作細節有不少差異, 但概念是跟上面的範例一樣的.