服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C# Random类的正确应用方法

C# Random类的正确应用方法

2022-10-18 11:48gt1987 C#

这篇文章主要介绍了C# Random类的正确应用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Random类介绍

Random类一个用于产生 伪随机 数字的类。这里的伪随机表示有随机性但是可以基于算法模拟出随机规律。

Random类的构造方式有两种。

  • Random r= new Random()。会以当前系统时间作为默认种子构建一个随机序列
  • Random r = new Random(unchecked((int)DateTime.Now.Ticks));。自定义一个种子,通常会使用时间Ticks。

随机性保证

由于Random的 伪随机 性,所以如果多个Random随机序列生成的时间间隔很短(官方说法15ms内),那么他们产生的随机数会大概率相同。如下列代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
 /// 错误的Random构建。
 /// </summary>
 public static void Bad_Random()
 {
   //正确做法应当将 Random构建防止循环外。
   //Random创建间隔时间极短的情况下,随机算法序列会基本一致,倒是随机性也是一致的
   //var r = new Random();
   for (int i = 0; i < 10; i++)
   {
     var r = new Random();
     var val = r.Next(1, 100);
     Console.WriteLine(val);
   }
 }

运行结果:

C# Random类的正确应用方法

所以在生产中通常可以考虑将Random单例化,以保证其随机算法的序列独一性。这也是官方推荐的方式。

Instead of instantiating individual Random objects, we recommend that you create a single Random instance to generate all the random numbers needed by your app.

这个问题在.net core下官方组件已对Random的构建作优化,所以上面的案例代码如果放在.net core项目下运行,你会发现可以正确的生成随机数。有兴趣的小伙伴可以自己尝试一下。不过为了代码的延续性,还是建议Random作为单例模式设计。

那么将Random设计为单例是否就解决了随机性的问题了呢,这时候就涉及到另外一个问题,Random不是线程安全的。如下列代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/// <summary>
/// 生成一个10位随机数
/// 设定了一定的复杂性,保证单线程下随机数不重复
/// </summary>
/// <param name="random">Random.</param>
/// <returns>随机数.</returns>
private static string GenerateRandomStr(Random random)
{
  string source = "ABCDEFGHIKLMNOPQRTUVWXYZabcdefghiklmnopqrtuvwxyz";
  int length = 10;
  var list = Enumerable.Repeat(source, length)
     .Select(s => s[random.Next(s.Length)]).ToArray();
  return new string(list);
}
/// <summary>
/// 单线程基本可以保证唯一性
/// </summary>
public static void Good_Random_In_SingleThread()
{
  //正确做法应当将 Random构建防止循环外。
  //Random创建间隔时间极短的情况下,随机算法序列会基本一致,倒是随机性也是一致的
  var r = new Random();
  ConcurrentBag<string> list = new ConcurrentBag<string>();
  for (int i = 0; i < 20000; i++)
  {
    var val = GenerateRandomStr(r);
    list.Add(val);
  }
 
  Console.WriteLine($"单线程下重复数据有:{20000 - list.Distinct().Count()}");
}
 
/// <summary>
/// 多线程下的Random构建。
/// Bad案例,Random非线程安全
/// 多线程高并发情况下,会出现概率重复
/// </summary>
public static void Bad_Random_In_MultThreads()
{
  var r = new Random(unchecked((int)DateTime.Now.Ticks));
  ConcurrentBag<string> list = new ConcurrentBag<string>();
 
  var t1 = Task.Run(() =>
  {
    for (int i = 0; i < 10000; i++)
    {
      var val = GenerateRandomStr(r);
      list.Add(val);
    }
  });
 
  var t2 = Task.Run(() =>
  {
    for (int i = 0; i < 10000; i++)
    {
      var val = GenerateRandomStr(r);
      list.Add(val);
    }
  });
 
  Task.WaitAll(t1, t2);
 
  Console.WriteLine($"线程1和线程2的重复数据有:{20000 - list.Distinct().Count()}");
}

运行结果:

C# Random类的正确应用方法

这种重复率在生产环境上是不可接受的。那么产生的原因是什么呢?根源还是在 伪随机线程不安全 上。我们可以想象下,一个Random实例中基于随机算法产生的一个随机数序列,在单线程下pop出一个随机数,然后指向下一个随机数。而在高并发的多线程情况下,指向下一个随机数的动作还未完成时,另一个线程又来请求pop,这样相同的随机数被重复pop了。

网上有很多多线程下Random的解决方案,我查阅了一些感觉都不是很好。以下是我的解决方案。用到了 ThreadLocal 。这个类详细的作用大家可以自己去查阅,这里大家只需要知道这个类可以保证它包含的对象只能线程内独享。简单说,同一类型对象 每个线程都独有一个Random实例互不影响。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//利用ThreadLocal 实现每个线程下Random独有
 //再通过seed原子性变更,保证每个Random的seed不同而生成的随机数列也不同
 private static int seed = 100;
 private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref seed)));
 
 /// <summary>
 /// 多线程下的Random构建。
 /// </summary>
 public static void Good_Random_In_MultThreads()
 {
   ConcurrentBag<string> list = new ConcurrentBag<string>();
 
   var t1 = Task.Run(() =>
   {
     for (int i = 0; i < 10000; i++)
     {
       var val = GenerateRandomStr(threadLocal.Value);
       list.Add(val);
     }
   });
 
   var t2 = Task.Run(() =>
   {
     for (int i = 0; i < 10000; i++)
     {
       var val = GenerateRandomStr(threadLocal.Value);
       list.Add(val);
     }
   });
 
   Task.WaitAll(t1, t2);
 
   Console.WriteLine($"[ThreadLocal模式]线程1和线程2的重复数据有:{20000 - list.Distinct().Count()}");
 }

运行结果:

C# Random类的正确应用方法

由此可见,基于ThreadLocal的特性,并区别了每个线程下的seed都不一样,从而保证每个Random的随机性也不行一样。

那么到这里Random的随机性问题解决了吗??

再深入思考下,对于集群部署情况,多台服务器同时运行,上述的Random随机性能保证吗?聪明的小伙伴应该能想到在不同服务器上,由于初始seed相同,可能又导致Random的随机性相同的情况发生。

那么解决方案也很简单,保证每台服务器的初始seed不同即可。这里的解决方案很多,不限于机器编号、IP地址后几位、启动时间(Environment.TickCount)等等。

这样,到这里Random的随机性问题终于可以告一段落了。

到此这篇关于C# Random类的正确应用方法的文章就介绍到这了,更多相关C# Random类内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/gt1987/p/14034660.html

延伸 · 阅读

精彩推荐
  • C#C#实现BBcode转为Markdown的方法

    C#实现BBcode转为Markdown的方法

    这篇文章主要给大家介绍了关于C#实现BBcode转Markdown的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需...

    lindexi9512022-02-20
  • C#C# 中闭包(Closure)详解

    C# 中闭包(Closure)详解

    这篇文章主要介绍了C# 中闭包(Closure)详解的相关资料,需要的朋友可以参考下...

    123si9902022-01-06
  • C#C#使用正则表达式抓取网站信息示例

    C#使用正则表达式抓取网站信息示例

    这篇文章主要介绍了C#使用正则表达式抓取网站信息,结合实例形式分析了C#针对网页信息的正则抓取操作相关技巧,具有一定参考借鉴价值,需要的朋友可以参...

    pan_junbiao4082021-12-20
  • C#Unity3D实现攻击范围检测

    Unity3D实现攻击范围检测

    这篇文章主要为大家详细介绍了Unity3D实现攻击范围检测,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    BattleTiger4392022-09-05
  • C#C# winform登陆框验证码的实现方法

    C# winform登陆框验证码的实现方法

    这篇文章主要为大家详细介绍了C# winform登陆框验证码的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    viu7532022-01-20
  • C#基于C#实现简单离线注册码生成与验证

    基于C#实现简单离线注册码生成与验证

    本文使用RSA非对称加密和Base64简单地实现离线注册码的生成与验证功能。感兴趣的朋友跟着小编一起学习吧...

    C#教程网4092021-10-28
  • C#C#实现五子棋游戏

    C#实现五子棋游戏

    这篇文章主要为大家详细介绍了C#实现五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    steveliu139472022-02-19
  • C#说说C#的async和await的具体用法

    说说C#的async和await的具体用法

    本篇文章主要介绍了说说C#的async和await的具体用法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    tianmuxia7522022-01-21