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

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么 Random.Shared 是线程安全的相关的知识,希望对你有一定的参考价值。

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

以上是关于为什么 Random.Shared 是线程安全的的主要内容,如果未能解决你的问题,请参考以下文章

为什么原生的servlet是线程不安全的而Struts2是线程安全的?

ThreadLocal

集合类不安全操作

调试过多线程的都会懂!

Java 中线程安全问题

WPF 支持的多线程 UI 并不是线程安全的