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

为什么 Random.Shared 是线程安全的的更多相关文章

  1. 为什么要使用ThreadLocalRandom代替Random生成随机数

    799 java里有伪随机型和安全型两种随机数生成器,伪随机生成器根据特定公式将seed转换成新的伪随机数据的一部分,安全随机生成器在底层依赖到操作系统提供的随机事件来生成数据. 安全随机生成器 需要 ...

  2. ThreadLocal线程范围内的共享变量

    模拟ThreadLocal类实现:线程范围内的共享变量,每个线程只能访问他自己的,不能访问别的线程. package com.ljq.test.thread; import java.util.Has ...

  3. Day10 多线程理论 开启线程

    多线程: 多线程和多进程的不同是他们占用的资源不一样, 一个进程里边可以包含一个或多个进程, 进程的开销大,线程的开销小. 打个比方来说:创建一个进程,就是创建一个车间.创建一个线程,就是在一个车间创 ...

  4. JAVA并行异步编程,线程池+FutureTask

    java 在JDK1.5中引入一个新的并发包java.util.concurrent 该包专门为java处理并发而书写. 在java中熟悉的使用多线程的方式为两种?继续Thread类,实现Runnal ...

  5. 模拟线程安全的售票案例(java)

    package try51.thread.safe; import java.util.ArrayList; import java.util.Random; import java.util.con ...

  6. 线程 Z

    原文:http://www.albahari.com/threading/part5.aspx 专题:C#中的多线程 1并行编程Permalink 在这一部分,我们讨论 Framework 4.0 加 ...

  7. 模拟登陆,selenium,线程池

    一 . 模拟登陆案例(识别验证码)  1 . 打码平台 - 云打码 : www.yundama.com  使用步骤 : - 注册两个账户,普通用户和开发者用户 : - 登陆 普通用户查看余额 登陆开发 ...

  8. Python网络爬虫之cookie处理、验证码识别、代理ip、基于线程池的数据爬去

    本文概要 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时, ...

  9. CUDA线程

    建议先看看前言中关于存储器的介绍:点击打开链接 线程 首先介绍进程,进程是程序的一次执行,线程是进程内的一个相对独立的可执行的单元.若把进程称为任务的话,那么线程则是应用中的一个子任务的执行.举个简单 ...

  10. python线程间通信

    #!/usr/bin/python # -*- coding:utf8 -*- from threading import Thread, Lock import random def test_th ...

随机推荐

  1. Elasticsearch:管理 Elasticsearch 内存并进行故障排除

    文章转载自:https://elasticstack.blog.csdn.net/article/details/116974695

  2. 使用scrapy爬取长安有妖气小说

    目标网站:https://www.snwx3.com/txt/434282.html 第一章地址:https://www.snwx3.com/book/434/434282/92792998.html ...

  3. 类和实例,super()函数

    class Foo: def __init__(self, name): self.name = name def ord_func(self): """定义实例方法,至 ...

  4. windows下mysql的数据主主同步

    mysql主主备份: 保证各服务器上的数据库中的数据一致,因此需要开启数据库同步机制.由于是一整套系统,并且系统内含数据库.由于任何一台服务器都有可能被选中,因此要让所有的数据库上的数据都是最新的,任 ...

  5. 图解不同版本的HTTP协议

    前言 大家好,我是蜗牛,今天我们聊聊HTTP协议,通过这篇文章我们能了解到不同版本HTTP优缺点.他们之间的性能差异以及现在主流的HTTP协议用的那个版本 HTTP/1.1 时代 HTTP/1.1 对 ...

  6. HTML+CSS基础知识(5)相对定位、绝对定位、固定定位

    文章目录 1.相对定位 1.1 代码 1.2 测试结果 2.绝对定位 2.1 代码 2.2 测试 3.固定定位 3.1 代码 3.2 测试结果 1.相对定位 1.1 代码 <!DOCTYPE h ...

  7. breakout靶机

    breakout:https://www.vulnhub.com/entry/empire-breakout,751/ 开机显示ip也可以不用扫描 首先使用nmap扫描 去访问网页 使用dirb扫描这 ...

  8. 11.mixins混合类

      一.混合类(mixins) 使用基于类的视图,最大的优势之一就创建可复用的代码 我们编写的非常类似的代码,可以抽象出来,将这部分代码放到mixin类系列中,然后作为父类提供子类继承使用 from ...

  9. Java多线程-ThreadPool线程池(三)

    开完一趟车完整的过程是启动.行驶和停车,但老司机都知道,真正费油的不是行驶,而是长时间的怠速.频繁地踩刹车等动作.因为在速度切换的过程中,发送机要多做一些工作,当然就要多费一些油. 而一个Java线程 ...

  10. vue传值

    在vue 中组件间的传参是必不可少的,下面说下几种传参方式 1.父组件传值给子组件,首先父组件发送的形式是用bind(用缩写:)绑定值到子组件身上.然后子组件用属性props接收 2.子组件传值父组件 ...