解释杨中科随机数为什么会骗人?

当你在Stack
Overflow网站标题中看到“随机”这个词你基本可以确定这是相同的基本问题无数的相似问题。本文带你探讨为什么随机性会引起这么多问题并且如何解决它们。

Stack Overflow (or newsgroup, or mailing list etc) )网站的问题通常是这样的:

我使用Random.Next生成随机数,但它一直给我相同的号码。 它不停的运行,但每次它会产生相同数量很多次。

这是由于这样的代码:


  // Bad code! Do not use!

for (int i = 0; i < 100; i++)

{

Console.WriteLine(GenerateDigit());

}

....

static int GenerateDigit()

{

Random rng = new Random();

// Assume there‘d be more logic here really

return rng.Next(10);

}

那么,这程序到底出了什么问题?

1.解读

这种Random类不是真正的随机数发生器,它是一个伪随机数发生器。任何Random实例都有一定量的状态,而当你调用Next( or NextDouble
or NextBytes),它会使用该状态来返回到似乎是随机的数据,相应的改变它内部状态以便于在下一步调用时你将得到另一个伪随机数。

所有的这一切都是确定的,如果你开始一个Random的实例以相同的初始状态(可通过种子来提供),并使用相同的序列方法调用它,那你会得到相同的结果。

那么在我们的示例代码中到底出了什么问题?
我们使用的一个新的Random实例也在循环迭代。随机无参数的构造函数取当前日期和时间作为种子-在内部定时器工作之前你通常可以执行大量代码,当前的日期和时间就会发生变化。
因此,我们重复使用相同的种子就会重复得到相同的结果。

2.对此我们能做什么?

这个问题有很多的解决方案, 其中有些方法是比其他的更好。
让我们先挑出其中一种方法,因为它不同于其他的方法。

3.使用加密的随机数发生器

  .NET有一个RandomNumberGenerator类应该是所有加密随机数生成器派生而来的抽象类。 这个框架本身附带了一个这样的派生类:
RNGCryptoServiceProvider 。 加密随机数发生器的理念是,即使它可能仍然是一个伪随机生成器,它还是很难做到不可预料。
内置的实现需要多个熵源在你的电脑有效地呈现“噪音”,并难以预测。它可以使用这种噪音不仅仅是计算一个种子,也可以在生成下一个数字时让你知道当前的状态,这也许可能不足以预测下一个结果(或者那些已经生成),这主要取决于具体的实施。Windows也可以利用专业硬件资源的随机性(如一块硬件观察放射性同位素衰变),从而使得随机数发生器更加安全。

  相比于这种随机,如果你看到(说)10个结果调用Random.Next(100)并投入大量计算资源任务,你可能会制定出最初的种子并预知接下来的结果将是...很有可能也会知道之前的结果是什么。
如果这种随机数应用于证券或金融的目的,这会是灾难性的事态。 加密随机数生成器通常比Random慢 ,但它在赋予数字难以预测和独立方面做得更好。

  在很多情况下,随机数生成器的性能不是一个问题-但有一个适当的API就会出现问题。 随机数字生成器设计基础仅此是用来生成随机字节。比较这种API的随机
,它可以让你请求一个随机整数,或随机double,或一组随机字节。我经常发现我需要一个整数的范围,得到可靠且一致地随机字节数组是很重要的。这不是不可能,但至少你可能会想要一个适配器类在随机数字生成器上。大多情况下,如果你能避免前面所述的陷阱,伪随机性的Random是可以接受的。

  让我们看看如何能做到这一点。

4.用一个复用的实例Random

对于“大量重复的数字”的修复程序的核心是重复使用同一个实例Random。
这听起来很简单...例如,我们可以改变我们这样原始的代码像这样:


// Somewhat better code...

Random rng = new Random();

for (int i = 0; i < 100; i++)

{

Console.WriteLine(GenerateDigit(rng));

}

...

static int GenerateDigit(Random rng)

{

// Assume there‘d be more logic here really

return rng.Next(10);

}

  现在,我们的循环会打印不同的数字......但我们还没有完成。假如你在快速连续的时间内调用此代码会发生什么?
我们可能仍然需要创建的两个Random实例使用相同的种子......虽然数字的每个字符串将包含不同的数字,我们可以很容易得到的数字相同的字符串的两倍。

  有两种方式可以避免这个问题。
一种方式是使用一个静态字段保持的单个实例Random被每一个对象使用。另外,我们可以推高实例,当然是最终达到计划时,这永远只能实例化一个单一的元素随机性
,并将其传递到任意地方。这是一个不错的主意(和它所表达的依赖性很好),但它不会完全的工作......至少,如果你的代码使用多个线程它会引发问题。

5.线程安全

  Random不是线程安全的。这是一个真正的痛处,因为考虑到我们观念上是想在任何程序中如何使用单个实例。
但事实是,如果你从多个线程使用相同实例,它很可能以全零内部状态结束,此时该实例变得无用。

  再次,在这里有两种方法可以解决这个问题。其一是仍然使用一个实例,
而且使用的每个调用方必须记住他们所使用的随机数生成器,同时获得锁。通过使用一个包装器锁定你就可以达到简化的效果,但在一个高度多线程系统中你仍然有可能浪费大量的时间等待加锁。

  在这里我们将学会另一种方法  - 是让每个线程有一个实例。
我们需要确保,当我们创建实例时我们不要重复使用相同的种子(例如,所以我们不能只调用无参数的构造函数),但除此之外它是相对简单的。

6.一个安全驱动

  很幸运的是,新ThreadLocal<T> .NET4类使得它很容易在每个线程需要单个实例中编写提供者。
您只需给ThreadLocal<T>构造一个委托调用来获得初始值当你不在的时候。
就我而言,我选择使用一个单一的种子变量,初始化使用Environment.TickCount(就像参数的Random构造函数),然后每递增,我们需要一个新的随机数生成器的时间-这是每一次的线程。

  整个类是静态的,只有一种公开方法: 随机获得线程
。这是一个方法而不是一个属性大多为方便起见:而不是让其中需要随机数的类依赖于Random本身,他们会依赖于Func<Random> 。
如果这类型仅设计在单个线程中运行,它可以调用委托获得的单个实例Random和重复使用; 假如它能够从多个线程中每次使用调用委托它就需要一个随机数发生器。
这将只会创造尽可能多的实例有线程,每个将使用不同的种子开始。 在依赖传球的时候,我们就可以用一个方法转换:

new TypeThatNeedsRandom(RandomProvider.GetThreadRandom) 下面的代码:


using System;

using System.Threading;

public static class RandomProvider

{

private static int seed = Environment.TickCount;

private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>

new Random(Interlocked.Increment(ref seed))

);

public static Random GetThreadRandom()

{

return randomWrapper.Value;

}

}

  很简单,不是吗? 这是因为它的所有关注的是提供正确的Random实例 。 它并不在乎你采用什么样方法调用已经获取的实例。
代码仍然可以滥用这个类,当然,通过存放一个随机引用并用多个线程重复使用它,但要做对的事还是很容易的。

7.界面设计问题

  一个问题仍然存在:这依旧不是很安全的。
正如我前面提到的,最常用的派生类是RNGCryptoServiceProvider,还有一个更安全随机数字发生器的版本,然而这个API在一般情况下还是很难使用。

  假如框架驱动已经从“我想以简单的方法得到一个随机值”的概念中分离概念的“随机性源”,这确实是令人非常愉快的。
然后我们可以根据需要使用一个简单的API来支持一个安全的或不安全的随机源,很不幸的是,还没有这样的方法。也许在将来的迭代中......或者有个第三方会想出一个适配器来代替。(可惜这在我能力之上,很好地做好这件事情是相当困难的。)你几乎可以轻松成功地派生随机和覆盖示例及下个字节
......但目前还不清楚他们需要如何工作,甚至Sample可能会非常棘手。 也许下一次...

  这是一篇国外的文章,被我翻译过来。原文地址:http://csharpindepth.com/Articles/Chapter12/Random.aspx

  接受批评指正,拒绝无脑喷粪。

时间: 2024-08-28 17:21:28

解释杨中科随机数为什么会骗人?的相关文章

跟着杨中科循序渐进学习wpf(全)

第一季 C#编程基础 1.用C#编写一个10+20=?的小程序: public static voidMain(tring[] args) { int i1=10; int i2=20; int i3=i1+i2; Console.WriteLine(i3);           //也可用占位符来实现:Console.WriteLine("{0}+{1}={2}",i1,i2,i1+i2);在输出参数较多时候要用占位符 Console.ReadKey();             

杨中科与如鹏网网友的对话—阐述了C、C++、Java之学习与程序化的思维

[如鹏网网友]:请问老师,C要学到什么程度才能去学JAVA之类的面向对象的语言呢?[杨中科]:能写有一定难度的程序.比如写一个俄罗斯方块.聊天软件什么的.面向对象的核心还是面向过程,面向过程都没学好呢,理解面向对象纯属越学越糊涂[如鹏网网友]:对,当初就是C都没学好,所以学C++也是越学越累[杨中科]:我反感大学这一点.上学期学C.下学期学C++.完全违背学习规律.应该在学完C后安排一个学期的实战开发课.像咱们如鹏网的课那样.积累的足够的开发经验,明白了“一切语言.面向对象都是纸老虎”以后再学其

跟着杨中科学习asp.net之dom

Dom教程 使用javascript操作dom进行dhtml开发,目标:能够使用javascript操作dom实现常见的dhtml效果 Dom就是html页面的模型,将每个标签都做成为一个对象 ,javascript通过调用dom中的属性.方法就可以对网页中的文本框.层等元素进行编程控制,比如通过操作文本框的dom对象,就可以读取文本框中的值.设置文本框中的值 Dom也像winform一样,通过事件.属性.方法进行编程 Javascript→dom就是c#→.net framework. Css

跟着杨中科学习asp.net之javascript

Dom教程 使用javascript操作dom进行dhtml开发,目标:能够使用javascript操作dom实现常见的dhtml效果 Dom就是html页面的模型,将每个标签都做成为一个对象 ,javascript通过调用dom中的属性.方法就可以对网页中的文本框.层等元素进行编程控制,比如通过操作文本框的dom对象,就可以读取文本框中的值.设置文本框中的值 Dom也像winform一样,通过事件.属性.方法进行编程 Javascript→dom就是c#→.net framework. Css

跟着杨中科学习asp.net之html

第一节课 HTML基础加强班 l 什么是浏览器? 1. 浏览器就是接受浏览者的操作(打开一个网址.点击一个链接.点击一个按钮),然后帮浏览者去web服务器请求网页的内容(html格式返回),然后展现成人眼能够看得懂的可视化的页面的软件. l IE=浏览器?这个说法是错误的.IE是浏览器的一种,还有FireFox.Opera.Charome等,注意遨游(Maxthon).世界之窗.搜狗浏览器.360浏览器等并不是一种独立于IE的浏览器,其内核还是IE的内核,只不过是换了一个外壳而已,所以用遨游的不

传智播客数据绑定和数据库开发基础(第四季)-杨中科

(一)数据绑定.ListBox.DataGrid SQLServer基础.SQLServer使用主键策略 (二)DataReader.DataSet.参数化查询.防注入漏洞攻击.SQLHelper 用户界面中进行登录判断.输错三次禁止登陆(半小时),用数据库记录ErrorTimes. 数据导入:从文本文件导入用户信息.易错点:Parameter的重复添加.File.ReadAllLines() 数据导出:将用户信息导出到文本文件.File.WriteAllLines() 省市联动选择 手机号码归

杨中科--数学的应用--识别色情图片

图形学领域一直是微积分.线性代数.概率这大学三大数学课程的经典应用场所.下面是其中一个应用“色情图片识别”相关资料,网上看到的,仅供参考. 2.jpg (40.89 KB, 下载次数: 71) 下载附件 2009-6-15 23:50 上传

杨中科板的代码生成器源码板

Form1.Designer.cs namespace itcastcoder { partial class Form1 { /// <summary> /// 必需的设计器变量. /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// 清理所有正在使用的资源. /// </summary> /// <param name

杨中科解惑这么多技术我该怎么学

经常有同学问“这么多技术我该怎么学,某某和某某两个技术哪个更有前途”.因此我写了下面的小文章,也算是和如鹏网所有同学的一个交流常见总结吧.一.这多东西啥时候能学完?现在IT新技术日新月异.就常用编程语言而言,有c/c++.汇编.java,c#.Python等:操作系统平台有unix/linux,windows系列:开发工具有VC.VisualStudio2008.Eclipse.NetBeans等:每个大平台下,还有很多的的方向:如网络.数据库.脚本.HTML.动态网站.游戏开发等:有人还在学D