C# Random類的正確應(yīng)用方法
Random類介紹
Random類一個(gè)用于產(chǎn)生 偽隨機(jī) 數(shù)字的類。這里的偽隨機(jī)表示有隨機(jī)性但是可以基于算法模擬出隨機(jī)規(guī)律。
Random類的構(gòu)造方式有兩種。
Random r= new Random()。
會(huì)以當(dāng)前系統(tǒng)時(shí)間作為默認(rèn)種子構(gòu)建一個(gè)隨機(jī)序列Random r = new Random(unchecked((int)DateTime.Now.Ticks));。
自定義一個(gè)種子,通常會(huì)使用時(shí)間Ticks。
隨機(jī)性保證
由于Random的 偽隨機(jī) 性,所以如果多個(gè)Random隨機(jī)序列生成的時(shí)間間隔很短(官方說法15ms內(nèi)),那么他們產(chǎn)生的隨機(jī)數(shù)會(huì)大概率相同。如下列代碼
/// <summary> /// 錯(cuò)誤的Random構(gòu)建。 /// </summary> public static void Bad_Random() { //正確做法應(yīng)當(dāng)將 Random構(gòu)建防止循環(huán)外。 //Random創(chuàng)建間隔時(shí)間極短的情況下,隨機(jī)算法序列會(huì)基本一致,倒是隨機(jī)性也是一致的 //var r = new Random(); for (int i = 0; i < 10; i++) { var r = new Random(); var val = r.Next(1, 100); Console.WriteLine(val); } }
運(yùn)行結(jié)果:
所以在生產(chǎn)中通??梢钥紤]將Random單例化,以保證其隨機(jī)算法的序列獨(dú)一性。這也是官方推薦的方式。
Instead of instantiating individual Random objects, we recommend that you create a single Random instance to generate all the random numbers needed by your app.
這個(gè)問題在.net core下官方組件已對(duì)Random的構(gòu)建作優(yōu)化,所以上面的案例代碼如果放在.net core項(xiàng)目下運(yùn)行,你會(huì)發(fā)現(xiàn)可以正確的生成隨機(jī)數(shù)。有興趣的小伙伴可以自己嘗試一下。不過為了代碼的延續(xù)性,還是建議Random作為單例模式設(shè)計(jì)。
那么將Random設(shè)計(jì)為單例是否就解決了隨機(jī)性的問題了呢,這時(shí)候就涉及到另外一個(gè)問題,Random不是線程安全的。如下列代碼
/// <summary> /// 生成一個(gè)10位隨機(jī)數(shù) /// 設(shè)定了一定的復(fù)雜性,保證單線程下隨機(jī)數(shù)不重復(fù) /// </summary> /// <param name="random">Random.</param> /// <returns>隨機(jī)數(shù).</returns> private static string GenerateRandomStr(Random random) { string source = "ABCDEFGHIKLMNOPQRTUVWXYZabcdefghiklmnopqrtuvwxyz"; int length = 10; var list = Enumerable.Repeat(source, length) .Select(s => s[random.Next(s.Length)]).ToArray(); return new string(list); } /// <summary> /// 單線程基本可以保證唯一性 /// </summary> public static void Good_Random_In_SingleThread() { //正確做法應(yīng)當(dāng)將 Random構(gòu)建防止循環(huán)外。 //Random創(chuàng)建間隔時(shí)間極短的情況下,隨機(jī)算法序列會(huì)基本一致,倒是隨機(jī)性也是一致的 var r = new Random(); ConcurrentBag<string> list = new ConcurrentBag<string>(); for (int i = 0; i < 20000; i++) { var val = GenerateRandomStr(r); list.Add(val); } Console.WriteLine($"單線程下重復(fù)數(shù)據(jù)有:{20000 - list.Distinct().Count()}"); } /// <summary> /// 多線程下的Random構(gòu)建。 /// Bad案例,Random非線程安全 /// 多線程高并發(fā)情況下,會(huì)出現(xiàn)概率重復(fù) /// </summary> public static void Bad_Random_In_MultThreads() { var r = new Random(unchecked((int)DateTime.Now.Ticks)); ConcurrentBag<string> list = new ConcurrentBag<string>(); var t1 = Task.Run(() => { for (int i = 0; i < 10000; i++) { var val = GenerateRandomStr(r); list.Add(val); } }); var t2 = Task.Run(() => { for (int i = 0; i < 10000; i++) { var val = GenerateRandomStr(r); list.Add(val); } }); Task.WaitAll(t1, t2); Console.WriteLine($"線程1和線程2的重復(fù)數(shù)據(jù)有:{20000 - list.Distinct().Count()}"); }
運(yùn)行結(jié)果:
這種重復(fù)率在生產(chǎn)環(huán)境上是不可接受的。那么產(chǎn)生的原因是什么呢?根源還是在 偽隨機(jī) 和 線程不安全 上。我們可以想象下,一個(gè)Random實(shí)例中基于隨機(jī)算法產(chǎn)生的一個(gè)隨機(jī)數(shù)序列,在單線程下pop出一個(gè)隨機(jī)數(shù),然后指向下一個(gè)隨機(jī)數(shù)。而在高并發(fā)的多線程情況下,指向下一個(gè)隨機(jī)數(shù)的動(dòng)作還未完成時(shí),另一個(gè)線程又來(lái)請(qǐng)求pop,這樣相同的隨機(jī)數(shù)被重復(fù)pop了。
網(wǎng)上有很多多線程下Random的解決方案,我查閱了一些感覺都不是很好。以下是我的解決方案。用到了 ThreadLocal
。這個(gè)類詳細(xì)的作用大家可以自己去查閱,這里大家只需要知道這個(gè)類可以保證它包含的對(duì)象只能線程內(nèi)獨(dú)享。簡(jiǎn)單說,同一類型對(duì)象 每個(gè)線程都獨(dú)有一個(gè)Random實(shí)例互不影響。
//利用ThreadLocal 實(shí)現(xiàn)每個(gè)線程下Random獨(dú)有 //再通過seed原子性變更,保證每個(gè)Random的seed不同而生成的隨機(jī)數(shù)列也不同 private static int seed = 100; private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref seed))); /// <summary> /// 多線程下的Random構(gòu)建。 /// </summary> public static void Good_Random_In_MultThreads() { ConcurrentBag<string> list = new ConcurrentBag<string>(); var t1 = Task.Run(() => { for (int i = 0; i < 10000; i++) { var val = GenerateRandomStr(threadLocal.Value); list.Add(val); } }); var t2 = Task.Run(() => { for (int i = 0; i < 10000; i++) { var val = GenerateRandomStr(threadLocal.Value); list.Add(val); } }); Task.WaitAll(t1, t2); Console.WriteLine($"[ThreadLocal模式]線程1和線程2的重復(fù)數(shù)據(jù)有:{20000 - list.Distinct().Count()}"); }
運(yùn)行結(jié)果:
由此可見,基于ThreadLocal的特性,并區(qū)別了每個(gè)線程下的seed都不一樣,從而保證每個(gè)Random的隨機(jī)性也不行一樣。
那么到這里Random的隨機(jī)性問題解決了嗎??
再深入思考下,對(duì)于集群部署情況,多臺(tái)服務(wù)器同時(shí)運(yùn)行,上述的Random隨機(jī)性能保證嗎?聰明的小伙伴應(yīng)該能想到在不同服務(wù)器上,由于初始seed相同,可能又導(dǎo)致Random的隨機(jī)性相同的情況發(fā)生。
那么解決方案也很簡(jiǎn)單,保證每臺(tái)服務(wù)器的初始seed不同即可。這里的解決方案很多,不限于機(jī)器編號(hào)、IP地址后幾位、啟動(dòng)時(shí)間(Environment.TickCount)等等。
這樣,到這里Random的隨機(jī)性問題終于可以告一段落了。
到此這篇關(guān)于C# Random類的正確應(yīng)用方法的文章就介紹到這了,更多相關(guān)C# Random類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#調(diào)用sql2000存儲(chǔ)過程方法小結(jié)
這篇文章主要介紹了C#調(diào)用sql2000存儲(chǔ)過程的方法,以實(shí)例形式分別對(duì)調(diào)用帶輸入?yún)?shù)及輸出參數(shù)的存儲(chǔ)過程進(jìn)行了詳細(xì)分析,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10如何在datatable中使用groupby進(jìn)行分組統(tǒng)計(jì)
如何在datatable中進(jìn)行分組,并且計(jì)算分組后每組的數(shù)量,考慮了一下,可以使用LINQ來(lái)實(shí)現(xiàn)datatable分組,需要的朋友可以參考下2015-08-08C#使用Socket進(jìn)行簡(jiǎn)單的通訊的示例代碼
Socket 類是基于與 Linux、macOS 或 Windows 的本機(jī)互操作性提供的托管代碼版本的套接字服務(wù),提供了一系列的接口來(lái)支持應(yīng)用層的調(diào)用,下面我們就來(lái)學(xué)習(xí)一下如何使用Socket進(jìn)行簡(jiǎn)單的通訊,需要的可以參考下2023-12-12基于Avalonia實(shí)現(xiàn)自定義彈窗的示例詳解
對(duì)于使用avalonia的時(shí)候某些功能需要到一些提示,比如異?;蛘叱晒Χ夹枰獙?duì)用戶進(jìn)行提示,所以需要單獨(dú)實(shí)現(xiàn)彈窗功能,并且可以自定義內(nèi)部組件,這一期將手動(dòng)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的小彈窗,并且很容易自定義,希望大家喜歡2023-02-02C#二進(jìn)制讀寫B(tài)inaryReader、BinaryWriter、BinaryFormatter
這篇文章介紹了C#二進(jìn)制讀寫B(tài)inaryReader、BinaryWriter、BinaryFormatter的用法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06C#實(shí)現(xiàn)在底圖上動(dòng)態(tài)生成文字和圖片
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)在底圖上動(dòng)態(tài)生成文字和圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05C#實(shí)現(xiàn)TreeView節(jié)點(diǎn)拖拽的方法
這篇文章主要介紹了C#實(shí)現(xiàn)TreeView節(jié)點(diǎn)拖拽的方法,涉及C#針對(duì)TreeView節(jié)點(diǎn)的動(dòng)態(tài)添加及移除技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09C#實(shí)現(xiàn)簡(jiǎn)單的文件加密與解密方式
這篇文章主要介紹了C#實(shí)現(xiàn)簡(jiǎn)單的文件加密與解密方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01