1

为什么 Random.Shared 是线程安全的

 1 year ago
source link: https://www.newbe.pro/ChatAI/Why-randome-shared-is-thread-safe/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

为什么 Random.Shared 是线程安全的

2022-12-10 2022-12-11 5

在多线程环境中使用 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 辅助注水浇筑而成,如有雷同,完全有可能。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK