為什麼 Random.Shared 是執行緒安全的

2022-12-12 12:01:09

在多執行緒環境中使用 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 屬性。它允許我們在多執行緒環境中安全地生成偽亂數,而不用擔心種子被意外修改的情況。

參考資料:

本文采用 Chat OpenAI 輔助注水澆築而成,如有雷同,完全有可能。