在多線程環境中使用 Random 類來生成偽隨機數時,很容易出現線程安全問題。例如,當多個線程同時調用 Next 方法時,可能會出現種子被意外修改的情況,導致生成的偽隨機數不符合預期。
為了避免這種情況,.NET 框架引入了 Random.Shared 屬性。它返回一個特殊的 Random 實例,可以在多線程環境中安全地生成偽隨機數。
代碼示例
下面是一個示例代碼,演示了 Random.Shared 屬性的使用方法:
using?System;
using?System.Threading;
using?System.Threading.Tasks;namespace?MyApp
{public?class?Program{public?static?void?Main(string[]?args){//?使用?Random.Shared?屬性創建一個新的?Random?實例var?random?=?Random.Shared;//?創建兩個新的?Task,分別用于生成偽隨機數var?task1?=?Task.Run(()?=>{//?生成偽隨機數for?(int?i?=?0;?i?<?5;?i++){//?調用?Next?方法生成偽隨機數var?number?=?random.Next();//?輸出當前線程的編號和生成的偽隨機數Console.WriteLine($"Thread1:?{Thread.CurrentThread.ManagedThreadId},?number?=?{number}");//?模擬耗時操作Thread.Sleep(500);}});var?task2?=?Task.Run(()?=>{//?生成偽隨機數for?(int?i?=?0;?i?<?5;?i++){//?調用?Next?方法生成偽隨機數var?number?=?random.Next();//?輸出當前線程的編號和生成的偽隨機數Console.WriteLine($"Thread2:?{Thread.CurrentThread.ManagedThreadId},?number?=?{number}");//?模擬耗時操作Thread.Sleep(500);}});//?等待兩個?Task?完成Task.WaitAll(task1,?task2);//?等待用戶輸入Console.ReadKey();}}
}
在上面的代碼中,我們使用 Random.Shared 屬性創建了一個新的 Random 實例,然后在兩個不同的線程中分別調用它的 Next 方法生成偽隨機數。由于 Random.Shared 屬性是線程安全的,所以兩個線程之間的訪問不會發生沖突,可以正常生成偽隨機數。
原理說明
Random.Shared 屬性返回的 Random 實例內部實際上使用了 [ThreadStatic] 屬性,來實現對種子的線程安全訪問。
[ThreadStatic] 屬性用于標識一個字段,表示該字段在每個線程中都有一個獨立的值。例如,如果一個字段被標記為 [ThreadStatic],那么每個線程都會有一個單獨的副本,它們之間互不影響。
舉個例子,假設我們有一個類,它有一個 [ThreadStatic] 字段:
public?class?MyClass
{[ThreadStatic]public?static?int?Counter;
}
在這個例子中,Counter 字段被標記為 [ThreadStatic],表示每個線程都有一個單獨的副本。例如,當我們在兩個不同的線程中訪問 Counter 字段時,實際上訪問的是兩個不同的副本,它們之間互不影響。
下面是一個示例代碼,演示了 [ThreadStatic] 屬性的使用方法:
using?System;
using?System.Threading;
using?System.Threading.Tasks;namespace?MyApp
{public?class?Program{public?static?void?Main(string[]?args){//?創建兩個新的?Task,分別用于訪問?Counter?字段var?task1?=?Task.Run(()?=>{//?訪問?Counter?字段for?(int?i?=?0;?i?<?5;?i++){//?增加?Counter?的值MyClass.Counter++;//?輸出當前線程的編號和?Counter?的值Console.WriteLine($"Thread1:?{Thread.CurrentThread.ManagedThreadId},?Counter?=?{MyClass.Counter}");//?模擬耗時操作Thread.Sleep(500);}});var?task2?=?Task.Run(()?=>{//?訪問?Counter?字段for?(int?i?=?0;?i?<?5;?i++){//?增加?Counter?的值MyClass.Counter++;//?輸出當前線程的編號和?Counter?的值Console.WriteLine($"Thread2:?{Thread.CurrentThread.ManagedThreadId},?Counter?=?{MyClass.Counter}");//?模擬耗時操作Thread.Sleep(500);}});//?等待兩個?Task?完成Task.WaitAll(task1,?task2);//?等待用戶輸入Console.ReadKey();}}
}
在上面的代碼中,我們創建了兩個新的 Task,分別用于訪問 Counter 字段。由于 Counter 字段被標記為 [ThreadStatic],所以兩個 Task 在不同的線程中執行,訪問的是兩個不同的副本。我們可以從輸出結果看出,兩個 Task 之間的修改不會影響到對方。
運行上面的代碼可能會得到類似下面的樣例結果:
Thread1:?Counter?=?1
Thread1:?Counter?=?2
Thread1:?Counter?=?3
Thread1:?Counter?=?4
Thread1:?Counter?=?5
Thread2:?Counter?=?1
Thread2:?Counter?=?2
Thread2:?Counter?=?3
Thread2:?Counter?=?4
Thread2:?Counter?=?5
可以看到,每個線程都會使用自己的 _counter 變量來記錄遞增的值,因此兩個線程之間的值是不同的。
以上是 [ThreadStatic] 屬性的使用方法。在 Random.Shared 屬性的實現中,也采用了類似的方法,來實現種子的線程安全訪問。由于每個線程都有一個單獨的種子,所以它們之間互不影響,并且也不會發生線程安全問題。
使用建議
在多線程環境中,我們建議使用 Random.Shared 屬性來生成偽隨機數。它能夠提供線程安全的保證,避免出現種子被意外修改的情況。
總結
通過使用 [ThreadStatic] 屬性,.NET 框架實現了線程安全的 Random.Shared 屬性。它允許我們在多線程環境中安全地生成偽隨機數,而不用擔心種子被意外修改的情況。
參考資料:
ThreadStaticAttribute Class
Random.Shared Property
本文采用 Chat OpenAI 輔助注水澆筑而成,如有雷同,完全有可能。