在多线程环境中使用 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. git commit 规范

  2. Intellij IDEA个人常用快捷键

    分享一下个人常用快捷键. 说明:字母排序规则遵循字母表(a->z) 快捷键 介绍 ctrl+b 快速打开当前光标处的类或方法 ctrl+d 复制当前光标所在行至下一行 ctrl+e 打开最近的文 ...

  3. PAT (Basic Level) Practice 1006 换个格式输出整数 分数 15

    让我们用字母 B 来表示"百".字母 S 表示"十",用 12...n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 3 位的正整数. ...

  4. MySQL用户也可以是个角色

    前言 角色(Role),可以认为是一些权限的集合,一直是存在各个数据库中,比如Oracle.SQL Server.OceanBase等,MySQL 自从 8.0 release 才引入角色这个概念. ...

  5. 把train数据集生成txt(test同理)

    import cv2 import numpy as np import os import sys import pickle data_dir = os.path.join("./&qu ...

  6. P7962 [NOIP2021] 方差 (DP)

    题目的意思就是可以交换差分数组,对答案进行化简:n∑ai2​−(∑ai​)2 ,再通过手玩分析可得最优解的差分数组一定是单谷(可以感性理解一下),因此我们将差分数组排序,依次加入,每次可以选择加在左边 ...

  7. POJ3237 Tree (树链剖分)

    通过打懒标记实现区间取反,和线段树基本操作都差不多. 本题还是一道边权化为点权的问题. 200行巨长代码: 1 #include<cstdio> 2 #include<cstring ...

  8. IDEA清空控制台以及Java中运行cmd命令实现清屏操作

    IDEA中清空控制台方法 在网上有看到各种的实现方法,比如: Runtime.getRuntime().exec("cls"); 或者: public static void cl ...

  9. Debian安装 WineHQ 安装包

    https://wiki.winehq.org/Debian_zhcn WineHQ 源仓库的密钥于 2018-12-19 改变过.如果您在此之前下载添加过该密钥,您需要重新下载和添加新的密钥并运行 ...

  10. MyBatisPlus 常用知识点总结

    @ 目录 完整的Mybatis-Plus项目 常用注解 设置表名(@TableName) 设置实体类字段 (@TableField) 通过 @TableField(fill=FieldFill.INS ...