在多线程环境中使用 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. 关联Prometheus与Alertmanager

    在Prometheus的架构中被划分成两个独立的部分.Prometheus负责产生告警,而Alertmanager负责告警产生后的后续处理.因此Alertmanager部署完成后,需要在Prometh ...

  2. 手把手教你使用LabVIEW人工智能视觉工具包快速实现传统Opencv算子的调用(含源码)

    前言 今天我们一起来使用LabVIEW AI视觉工具包快速实现图像的滤波与增强:图像灰度处理:阈值处理与设定:二值化处理:边缘提取与特征提取等基本操作.工具包的安装与下载方法可见之前的博客. 一.图像 ...

  3. Node.js(六)MongoDB

    student.js var express = require('express'); var router = express.Router(); const _=require("lo ...

  4. 从SVN导出项目出现的乱码问题

    解决的方法很简单,只需要将Eclipse的编码标准设置为UTF-8即可 1.Window->Preferences->General->Workspace   面板Text file ...

  5. Mysql知识点整理

    索引相关 abcd联合索引搜索ba会走索引么 会,重排 索引的底层实现是B+树,为何不采用红黑树,B树? (1):B+Tree非叶子节点只存储键值信息,降低B+Tree的高度,所有叶子节点之间都有一个 ...

  6. (数据科学学习手札145)在Python中利用yarl轻松操作url

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,在诸如网络爬虫.web应用开发 ...

  7. Mockito使用方法(Kotlin)

    一.为什么要使用Mockito 1.实际案例 1.1 遇到的问题 对于经常维护的项目,经常遇到一个实际问题:需求不停改变,导致架构经常需要修改某些概念的定义. 对于某些十分基础又十分常用的概念,常常牵 ...

  8. letcode刷题记录-day02-回文数

    回文数 题目描述 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答 ...

  9. .NET 7 中 LINQ 的疯狂性能提升

    LINQ 是 Language INtegrated Query 单词的首字母缩写,翻译过来是语言集成查询.它为查询跨各种数据源和格式的数据提供了一致的模型,所以叫集成查询.由于这种查询并没有制造新的 ...

  10. Java 多线程写zip文件遇到的错误 write beyond end of stream!

    最近在写一个大量小文件直接压缩到一个zip的需求,由于zip中的entry每一个都是独立的,不需要追加写入,也就是一个entry文件,写一个内容, 因此直接使用了多线程来处理,结果就翻车了,代码给出了 ...