随机数生成器仅生成一个随机数


765

我有以下功能:

//Function to get random number
public static int RandomNumber(int min, int max)
{
    Random random = new Random();
    return random.Next(min, max);
}

我怎么称呼它:

byte[] mac = new byte[6];
for (int x = 0; x < 6; ++x)
    mac[x] = (byte)(Misc.RandomNumber((int)0xFFFF, (int)0xFFFFFF) % 256);

如果在运行时与调试器一起执行该循环,则会得到不同的值(这是我想要的)。但是,如果我在该代码下两行放置一个断点,则mac数组的所有成员都具有相等的值。

为什么会这样呢?


20
使用new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);不会产生比“ .Next(0, 256)
bohdan_trotsenko

您可能会发现此NuGet软件包很有帮助。它提供了一种静态Rand.Next(int, int)方法,该方法提供对随机值的静态访问,而不会锁定或
遇到

Answers:


1040

每次您new Random()使用时钟初始化它。这意味着在紧密的循环中,您会多次获得相同的值。您应该保留一个Random实例,并在同一实例上继续使用Next

//Function to get a random number 
private static readonly Random random = new Random(); 
private static readonly object syncLock = new object(); 
public static int RandomNumber(int min, int max)
{
    lock(syncLock) { // synchronize
        return random.Next(min, max);
    }
}

编辑(请参阅评论):为什么我们需要lock这里?

基本上,Next将要更改Random实例的内部状态。如果我们同时在多个线程中执行此操作,则可能会争辩“我们只是使结果更加随机”,但实际上,我们正在做的事情有可能破坏内部实现,并且我们也可能开始获得相同的数字从不同的线程,这可能是一个问题-可能不是。但是,保证内部发生的事情是更大的问题。因为Random不是让线程安全的任何保证。因此,有两种有效的方法:

  • 同步,这样我们就不会同时从不同的线程访问它
  • Random每个线程使用不同的实例

两者都可以。但是同时使多个调用者的单个实例静音只会带来麻烦。

lock实现这些方法的第一(和更简单); 但是,另一种方法可能是:

private static readonly ThreadLocal<Random> appRandom
     = new ThreadLocal<Random>(() => new Random());

这是每个线程的,因此您不需要同步。


19
通常,所有静态方法都应具有线程安全性,因为很难保证多个线程不会同时调用它。这是不是通常必要进行实例(即非静态)方法是线程安全的。
Marc Gravell

5
@Florin-两者之间没有“基于堆栈”的区别。静态字段同样是“外部状态”,并且绝对会在调用方之间共享。使用实例时,不同线程具有不同实例(常见模式)的可能性很大。使用静态,可以保证它们全部共享(不包括[ThreadStatic])。
Marc Gravell

2
@gdoron您遇到错误了吗?“锁”应防止线程在这里彼此跳闸
Marc Gravell

6
@Dan,如果该对象从未公开公开:可以。(非常理论上的)风险是某些其他线程以您未曾想到的方式锁定了它。
Marc Gravell

3
@smiron很可能您也只是在锁之外使用随机数。锁定并不能阻止所有访问您所锁定的内容-只是确保同一实例上的两个lock语句不会同时运行。因此,lock (syncObject)只有在所有 random.Next()呼叫都在范围内时,它才会有所帮助lock (syncObject)。如果您描述的方案即使在正确lock使用的情况下也确实发生,则它也有可能在单线程方案中发生(例如,Random被巧妙地破坏)。
a安2015年

118

为了便于在整个应用程序中重复使用,静态类可能会有所帮助。

public static class StaticRandom
{
    private static int seed;

    private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>
        (() => new Random(Interlocked.Increment(ref seed)));

    static StaticRandom()
    {
        seed = Environment.TickCount;
    }

    public static Random Instance { get { return threadLocal.Value; } }
}

然后可以将静态随机实例与以下代码一起使用

StaticRandom.Instance.Next(1, 100);

62

Mark的解决方案可能非常昂贵,因为它需要每次都同步。

我们可以通过使用特定于线程的存储模式来解决同步需求:


public class RandomNumber : IRandomNumber
{
    private static readonly Random Global = new Random();
    [ThreadStatic] private static Random _local;

    public int Next(int max)
    {
        var localBuffer = _local;
        if (localBuffer == null) 
        {
            int seed;
            lock(Global) seed = Global.Next();
            localBuffer = new Random(seed);
            _local = localBuffer;
        }
        return localBuffer.Next(max);
    }
}

测量这两个实现,您应该会看到很大的不同。


12
在不使用锁的情况下,锁非常便宜……即使存在争议,我也希望“现在用数字做一些事情”代码在大多数有趣的情况下使锁的成本相形见war。
马克·格雷夫

4
同意,这解决了锁定问题,但这仍然不是一个琐碎问题的高度复杂的解决方案:您需要编写“两行”代码来生成一个随机数而不是一个。节省阅读一行简单代码真的值得吗?
EMP 2010年

4
+1使用其他全局Random实例获取种子是一个好主意。还要注意,可以使用ThreadLocal<T>.NET 4中引入的类进一步简化代码(正如Phil 在下面所述)。
Groo 2014年

40

从我的答案在这里

只是重申正确的解决方案

namespace mySpace
{
    public static class Util
    {
        private static rnd = new Random();
        public static int GetRandom()
        {
            return rnd.Next();
        }
    }
}

因此,您可以致电:

var i = Util.GetRandom();

全部。

如果您严格需要使用真正的无状态静态方法来生成随机数,则可以依赖Guid

public static class Util
{
    public static int GetRandom()
    {
        return Guid.NewGuid().GetHashCode();
    }
}

它将稍微慢一点,但Random.Next至少从我的经验来看,可能比随机得多。

不是

new Random(Guid.NewGuid().GetHashCode()).Next();

不必要的对象创建将使其变慢,尤其是在循环下。

而且永远不会

new Random().Next();

它不仅速度较慢(在循环内),而且随机性也很好。


10
我不同意Guid案。Random类实现均匀分布。在Guid中情况并非如此。指导的目标是唯一的,不均匀分布的(并且大多数情况下,其实现是基于某种硬件/机器属性,与...的随机性相反)。
Askolein

4
如果您不能证明Guid生成的均匀性,那么将其用作随机数是错误的(并且Hash将是远离均匀性的又一步)。同样,碰撞也不是问题:碰撞的均匀性是关键。关于Guid一代不再使用硬件,我要去RTFM了,我不好(有什么参考吗?)
Askolein

5
对“随机性”有两种理解:1. 缺少模式或2. 遵循概率分布描述的演化(模式1中包含2个)而缺少模式。您的Guid示例在情况1中正确,而在情况2中正确。相反:Random类与情况2相匹配(因此,情况1也是如此)。您只能替换的使用Random你的Guid+Hash,如果你没有的情况下,2情况1可能是足以回答这个问题,然后,你的Guid+Hash作品的罚款。但这并没有明确说明(ps:这种制服
Askolein

2
@Askolein就一些测试数据而言,我运行了两个批处理,RandomGuid.NewGuid().GetHashCode()通过Ent(fourmilab.ch/random)运行,并且它们都是类似随机的。new Random(Guid.NewGuid().GetHashCode())效果也一样,使用同步的“母版” Random为“子代”生成种子Random也是如此。甚至是加密随机。因此,如今Windows或MS SQL似乎还不错。但是,单声道和/或移动设备可能有所不同。
a安2015年

2
@EdB正如我在前面的评论中所说,尽管Guid(大量)是唯一的,但GetHashCode.NET中Guid的是从其字符串表示形式派生的。根据我的喜好,输出是相当随机的。
nawfal

27

我宁愿使用以下类来生成随机数:

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);

30
我不是拒绝投票的人之一,但请注意,标准PNRG确实可以满足真正的需要-即能够从已知种子中重复生成序列。有时,真正的加密RNG 的纯粹成本太高了。有时,需要加密RNG。可以讲课程的马。
Marc Gravell

4
根据文档,该类是线程安全的,因此很受青睐。
Rob Church

使用它们,两个随机字符串成为相同的概率是多少?如果字符串只有3个字符,我猜这很有可能会发生,但是如果长度为255个字符怎么办,可以有相同的随机字符串,还是可以保证算法不会发生这种情况?
Lyubomir Velchev '16

16

1)正如Marc Gravell所说,请尝试使用一个随机生成器。将其添加到构造函数中总是很酷的:System.Environment.TickCount。

2)一个提示。假设您要创建100个对象,并假设每个对象都应具有自己的随机生成器(如果在很短的时间内计算随机数的LOADS,就很方便)。如果要循环执行此操作(生成100个对象),则可以这样做(以确保完全随机):

int inMyRandSeed;

for(int i=0;i<100;i++)
{
   inMyRandSeed = System.Environment.TickCount + i;
   .
   .
   .
   myNewObject = new MyNewObject(inMyRandSeed);  
   .
   .
   .
}

// Usage: Random m_rndGen = new Random(inMyRandSeed);

干杯。


3
我将System.Environment.TickCount移出循环。如果在迭代时滴答作响,那么您将有两个项目初始化为相同的种子。另一种选择是以不同方式组合滴答计数i(例如System.Environment.TickCount << 8 + i)
Dolphin 2009年

如果我理解正确:您的意思是说,这可能发生,“ System.Environment.TickCount + i”可能导致相同的值吗?
萨比兰

编辑:当然,不需要在循环内有TickCount。我的错 :)。
萨比兰

2
无论如何,默认的Random()构造函数都会调用Random(Environment.TickCount)
Alsty

5

每次执行

Random random = new Random (15);

无论执行数百万次都没有关系,您将始终使用相同的种子。

如果您使用

Random random = new Random ();

如果黑客猜测种子并且您的算法与系统的安全性有关,则您将获得不同的随机数序列-您的算法已损坏。我执行死刑 在此构造函数中,种子由系统时钟指定,如果在很短的时间段(毫秒)内创建了多个实例,则它们可能具有相同的种子。

如果您需要安全的随机数,则必须使用该类

System.Security.Cryptography.RNGCryptoServiceProvider

public static int Next(int min, int max)
{
    if(min >= max)
    {
        throw new ArgumentException("Min value is greater or equals than Max value.");
    }
    byte[] intBytes = new byte[4];
    using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        rng.GetNonZeroBytes(intBytes);
    }
    return  min +  Math.Abs(BitConverter.ToInt32(intBytes, 0)) % (max - min + 1);
}

用法:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. 除非您自己指定种子,否则情况并非如此。
LarsTech '18 -10-19

搞掂。正是由于您说过LarsTech,如果始终指定相同的种子,则将始终生成相同的随机数序列。在我的回答中,如果您始终使用相同的种子,那么我将使用参数来引用构造函数。Random类仅生成伪随机数。如果有人发现您在算法中使用了哪种种子,则可能会损害算法的安全性或随机性。使用RNGCryptoServiceProvider类,您可以安全地拥有随机数。我已经纠正了,非常感谢您的纠正。
乔马(Joma)

0

只需这样声明Random类变量:

    Random r = new Random();
    // ... Get three random numbers.
    //     Here you'll get numbers from 5 to 9
    Console.WriteLine(r.Next(5, 10));

如果您想每次从列表中获得不同的随机数,请使用

r.Next(StartPoint,EndPoint) //Here end point will not be included

每次通过声明Random r = new Random()一次。


调用new Random()时使用系统时钟,但是如果在时钟更改之前连续两次调用整个代码,则会获得相同的随机数。这就是以上答案的重点。
野蛮

-1

有很多解决方案,这里是一种:如果只希望数字删除字母,则该方法将接收随机数和结果长度。

public String GenerateRandom(Random oRandom, int iLongitudPin)
{
    String sCharacters = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
    int iLength = sCharacters.Length;
    char cCharacter;
    int iLongitudNuevaCadena = iLongitudPin; 
    String sRandomResult = "";
    for (int i = 0; i < iLongitudNuevaCadena; i++)
    {
        cCharacter = sCharacters[oRandom.Next(iLength)];
        sRandomResult += cCharacter.ToString();
    }
    return (sRandomResult);
}

基本问题仍然相同-您正在传递Random实例,但是您仍然期望调用者创建一个共享实例。如果调用方每次都创建一个新实例,并且代码在时钟改变之前执行了两次,则您将获得相同的随机数。因此,此答案仍然做出可能是错误的假设。
野蛮

同样,具有生成随机数的方法的全部要点是封装-调用方法不必担心实现,它只想收回随机数
Savage
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.