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

Random number generator only generating one random number

提问人:Ivan Prodanov 提问时间:4/20/2009 最后编辑:Selim YildizIvan Prodanov 更新时间:3/22/2023 访问量:246338

问:

我有以下功能:

//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

为什么会这样?

C# 随机

评论

27赞 bohdan_trotsenko 3/18/2016
使用不会产生比new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);.Next(0, 256)
2赞 ChaseMedallion 5/8/2016
你可能会发现此 NuGet 包很有帮助。它提供了一种静态方法,该方法提供对随机值的静态访问,而不会锁定或遇到种子重用问题Rand.Next(int, int)

答:

1144赞 Marc Gravell 4/20/2009 #1

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

//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

基本上,将更改实例的内部状态。如果我们从多个线程同时这样做,你可能会争辩说“我们只是让结果更加随机”,但我们实际上所做的是破坏内部实现,我们也可能开始从不同的线程获得相同的数字,这可能是一个问题 - 也可能不是。然而,对内部发生的事情的保证是更大的问题;因为不对线程安全做出任何保证。因此,有两种有效的方法:NextRandomRandom

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

两者都可以;但是,同时将多个调用方的单个实例互斥只是自找麻烦。

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

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

然后,这是每个线程的,因此无需同步。

评论

26赞 Marc Gravell 4/20/2009
作为一般规则,所有静态方法都应该是线程安全的,因为很难保证多个线程不会同时调用它。通常不需要使实例(即非静态)方法线程安全。
5赞 Marc Gravell 4/20/2009
@Florin - 两者之间没有“基于堆栈”的区别。静态字段同样是“外部状态”,并且绝对会在调用者之间共享。对于实例,不同的线程很有可能具有不同的实例(常见模式)。使用静态时,可以保证它们都共享(不包括 [ThreadStatic])。
3赞 Dan Bechard 2/1/2014
为什么不能使用?lock(random)
6赞 Marc Gravell 2/1/2014
@Dan对象是否从未公开:您可以。(非常理论上的)风险是其他线程以您意想不到的方式锁定它。
4赞 Luaan 4/21/2015
@smiron 你很可能也只是在锁之外使用随机。锁定不会阻止对所锁定内容的所有访问 - 它只是确保同一实例上的两个 lock 语句不会同时运行。因此,只有当所有调用也在 .如果您描述的场景确实发生了,即使使用正确,它也有可能发生在单线程场景中(例如 被巧妙地打破了)。lock (syncObject)random.Next()lock (syncObject)lockRandom
28赞 fARcRY 4/20/2009 #2

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

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

评论

32赞 Marc Gravell 4/20/2009
我不是反对者之一,但请注意,标准 PNRG 确实满足了真正的需求——即能够重复地从已知种子中复制序列。有时,真正的加密 RNG 的绝对成本太高了。有时加密 RNG 是必要的。可以这么说,课程的马匹。
4赞 Rob Church 4/26/2013
根据文档,这个类是线程安全的,所以这对它有利。
0赞 Lyubomir Velchev 7/12/2016
使用它,两个随机字符串合二为一的概率是多少?如果字符串只有 3 个字符,我想这很可能会发生,但如果长度为 255 个字符,是否有可能具有相同的随机字符串,或者保证算法不会发生这种情况?
0赞 ToolmakerSteve 8/6/2020
@LyubomirVelchev - 从数学上讲,不可能设计一个函数(或一个硬件,甚至一个理论结构)来保证两个独立生成的有限长度的字符串永远不会相同。不可能是:选择的数量是有限的。给定 n 个可能的字符串,存在 - 并且必须有 - 两个独立字符串相同的 1/n 概率。(是的,这意味着任何加密方案都不是100%安全的;但是,如果在宇宙的生命周期中发生两次的几率足够低......在实践中足够好。
0赞 ToolmakerSteve 8/6/2020
Joma 后来的回答包含基于 RNGCryptoServiceProvider 的更完整的代码片段。看。但为了性能,请修改他的代码以将 移出方法 - 请参阅我在那里的评论。public static int Next(int min, int max) ...newNext
18赞 3 revs, 3 users 88%sabiland #3

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

2)一个提示。假设您要创建 100 个对象,并假设每个对象都应该有自己的随机生成器(如果您在很短的时间内计算随机数的负载,这很方便)。如果你在一个循环中这样做(生成 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);

干杯。

评论

4赞 Dolphin 6/26/2009
我会将System.Environment.TickCount移出循环。如果它在你迭代时滴答作响,那么你将有两个项目初始化为相同的种子。另一种选择是以不同的方式组合 tickcount 和 i(例如 System.Environment.TickCount<<8 + i)
0赞 sabiland 6/26/2009
如果我理解正确:您的意思是,“System.Environment.TickCount + i”可能会产生相同的值吗?
0赞 sabiland 6/26/2009
编辑:当然,不需要在循环中安装TickCount。我的坏:)。
3赞 Alsty 2/17/2017
默认构造函数仍然调用Random()Random(Environment.TickCount)
0赞 ToolmakerSteve 8/6/2020
@Alsty - 有用的观察 - 如果只是创建一个全局随机生成器。但是,如果在同一价格变动期间调用默认构造函数两次,则将获得两个随机生成器,每个生成器生成完全相同的随机数序列。可能不是你想要的!上面的逻辑 (#2) 使用 seeds , , etc - 所以生成器都是不同的Random()TickCount+0TickCount+1
68赞 Hans Malherbe 6/23/2009 #4

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);
    }
}

测量这两种实现,您应该会看到显着差异。

评论

16赞 Marc Gravell 9/18/2009
锁在没有争议的时候非常便宜......即使有争议,我也希望“现在对数字做点什么”代码在最有趣的情况下使锁的成本相形见绌。
4赞 EMP 4/16/2010
同意,这解决了锁定问题,但这不仍然是一个非常复杂的解决方案吗,解决了一个微不足道的问题:你需要编写“两行”代码来生成一个随机数而不是一个。为了节省阅读一行简单的代码,这真的值得吗?
5赞 vgru 5/9/2014
+1 使用额外的全局实例来获取种子是个好主意。另请注意,可以使用 .NET 4 中引入的类进一步简化代码(Phil 在下面也写道)。RandomThreadLocal<T>
0赞 ToolmakerSteve 8/6/2020
既然如此,你为什么要把它复制到/从?这是性能优化吗?也就是说,访问变量的性能是否比访问常规变量的成本高得多?(如果是这样,这可能会抵消在典型情况下相对于 的所谓优势。如果没有,则可以简化代码。_localThreadStaticvar localBufferThreadStaticlock
0赞 Hans Malherbe 9/24/2020
@ToolmakerSteve 是的,堆栈比 TSS 更快。与锁定相比,我并不担心成本,因为锁定引入了 100 到 1000 个周期。我的解决方案的问题是“If”语句引入的分支可能会花费 100+ 个周期,因为当分支预测器出错时会刷新管道和指令缓存。
135赞 Phil 7/13/2012 #5

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

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);
42赞 nawfal 3/31/2013 #6

我的答案是:

只是重申正确的解决方案

namespace mySpace
{
    public static class Util
    {
        private static Random 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();

它不仅速度较慢(在循环内),而且它的随机性是......好吧,在我看来不是很好。.

评论

17赞 Askolein 3/31/2013
我不同意Guid案。Random 类实现均匀分布。Guid 并非如此。Guid 的目标是唯一而不是均匀分布(并且它的实现大多数时候是基于一些硬件/机器属性,这与......随机性)。
6赞 Askolein 3/31/2013
如果你不能证明 Guid 生成的均匀性,那么将其用作随机是错误的(并且 Hash 将离均匀性又一步)。同样,碰撞也不是问题:碰撞的均匀性才是。关于 Guid 一代不再在硬件上,我要去 RTFM,我的坏(任何参考?
6赞 Askolein 4/1/2013
对“随机”有两种理解:1.缺乏模式或2。缺乏遵循概率分布描述的进化的模式(2 包含在 1 中)。您的 Guid 示例在案例 1 中是正确的,而不是在案例 2 中。相反:类与情况 2 匹配(因此,情况 1 也是如此)。只有在情况 2 中您才能替换 by your 的用法。案例 1 可能足以回答问题,然后,您的工作正常。但是没有说清楚(ps:这套制服RandomRandomGuid+HashGuid+Hash)
2赞 Luaan 4/21/2015
@Askolein 对于一些测试数据,我运行了几个批次的两者,并通过 Ent (fourmilab.ch/random) 运行,两者都是类似的随机的。 工作同样有效,使用同步的“主”为“子”生成种子也是如此。当然,这确实取决于您的系统如何生成 Guid——对于我的系统来说,它们是相当随机的,而在其他系统上,它甚至可能是加密随机的。所以现在Windows或MS SQL似乎很好。不过,单声道和/或移动设备可能有所不同。RandomGuid.NewGuid().GetHashCode()new Random(Guid.NewGuid().GetHashCode())RandomRandom
2赞 nawfal 8/4/2015
@EdB 正如我之前在评论中所说,虽然 Guid(一个大数字)是唯一的,但 .NET 中的 Guid 是从它的字符串表示形式派生的。输出对我来说是相当随机的。GetHashCode
-3赞 Marztres #7

有很多解决方案,这里有一个:如果你只想数字擦除字母,该方法会收到一个随机数和结果长度。

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);
}

评论

1赞 Savage 7/8/2019
基本问题仍然是一样的 - 您正在传入一个实例,但您仍然期望调用方创建一个共享实例。如果调用方每次都创建一个新实例,并且代码在时钟更改之前执行两次,则将获得相同的随机数。因此,这个答案仍然做出了可能错误的假设。Random
0赞 Savage 7/8/2019
此外,使用生成随机数的方法的全部意义在于封装 - 调用方法不必担心实现,它只对取回随机数感兴趣
10赞 Joma 10/20/2018 #8

每次执行时

Random random = new Random (15);

即使你执行数百万次也没关系,你将始终使用相同的种子。

如果您使用

Random random = new Random ();

你会得到不同的随机数序列,如果黑客猜到了种子,而你的算法与你的系统安全有关——你的算法被破坏了。我你执行 mult。在此构造函数中,种子由系统时钟指定,如果在很短的时间内(毫秒)创建多个实例,则它们可能具有相同的种子。

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

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);

评论

3赞 LarsTech 10/20/2018
It does not matter if you execute it millions of times, you will always use the same seed.除非您自己指定种子,否则情况并非如此。
0赞 Joma 10/20/2018
修好了。谢谢 正如您所说,如果始终指定相同的种子,则始终会生成相同的随机数序列。在我的回答中,如果您始终使用相同的种子,我将使用带有参数的构造函数引用。Random 类仅生成伪随机数。如果有人发现您在算法中使用的种子,它可能会损害算法的安全性或随机性。使用 RNGCryptoServiceProvider 类,可以安全地拥有随机数。我已经纠正了,非常感谢你的纠正。
0赞 ToolmakerSteve 8/6/2020
呼吁每一个.相反,声明 然后删除包装器;只需调用该静态即可。new RNGCryptoServiceProvider()Nextprivate static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();usingrng.GetNonZeroBytes(intBytes);
0赞 ToolmakerSteve 8/6/2020
回复 “The Random 类只生成伪随机数。” - 所有软件算法都会生成随机数序列。真正的随机性需要基于一些被认为是“真正随机”的物理现象的硬件。OTOH,加密算法经过精心设计(和测试)以改善生成序列的统计分布 - 以避免可能利用更简单随机生成器中的弱点的暴力攻击。尽管在许多用途上都矫枉过正,但我同意这提供了优越的统计分布。
-2赞 Paolo Barone 8/31/2020 #9

我通过使用 Rnd() 函数解决了这个问题:

Function RollD6() As UInteger
        RollD6 = (Math.Floor(6 * Rnd())) + 1
        Return RollD6
End Function

当表单加载时,我使用 Randomize() 方法来确保我不会总是从运行到运行获得相同的随机数序列。

评论

2赞 Peter O. 8/31/2020
这个问题是关于 C#,而不是 Visual Basic.NET。(尽管两者都是 .NET 语言,尽管从 C# 访问 VB 函数是可能的,但不是那么微不足道。
-5赞 Deependra Kushwah 10/1/2020 #10

始终获得一个正随机数。

 var nexnumber = Guid.NewGuid().GetHashCode();
        if (nexnumber < 0)
        {
            nexnumber *= -1;
        }

评论

0赞 bytedev 1/27/2021
此代码不使用 Random 对象,这在问题中很明显,并且 GUID 在技术上不是随机的(如上所述)。
-2赞 SZL 7/22/2021 #11

在 Visual Basic 中,这可行(可能可以转换为 C#,如果不是 DLL 引用可以成为解决方案):

Private Function GetRandomInt(ByVal Min As Integer, ByVal Max As Integer) As Integer
     Static Generator As System.Random = New System.Random()
     Return Generator.Next(Min, Max)
End Function
-1赞 SZL 7/22/2021 #12

我用这个:

int randomNumber = int.Parse(Guid.NewGuid().ToString().FirstOrDefault(Char.IsDigit).ToString().Replace("\0", "0"));

性能:在我的 PC 上生成 100 万个随机数:711 毫秒。

如果 Guid 不包含任何数字(我不知道这是否可能),则结果将使用 0。

0赞 Mark Cilia Vincenti 3/20/2022 #13

您可以使用如下代码:

public static class ThreadSafeRandom
{
    private static readonly Random _global = new Random();
    private static readonly ThreadLocal<Random> _local = new ThreadLocal<Random>(() =>
    {
        int seed;
        lock (_global)
        {
            seed = _global.Next();
        }
        return new Random(seed);
    });

    public static Random Instance => _local.Value;
}

此代码可以按原样使用,也可以通过 NuGet 包 ThreadSafeRandomizer 使用。

编辑:从.NET 6.0开始,您可以改用。您仍然可以使用上述包,该包在上述代码或预处理器指令之间进行选择。Random.Shared.Next()Random.Shared

3赞 Theodor Zoulias 8/1/2022 #14

从 .NET 6 开始,该类现在配备了一个名为 Shared 的静态属性:Random

提供线程安全的 Random 实例,可从任何线程并发使用。

你可以这样使用它:

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

访问线程安全对象的开销很小,因此,如果您计划 尽可能快地在单个线程上生成数百万个随机数,最好创建一个专用实例,而不是依赖 .RandomShared

1赞 Evgeny 9/9/2022 #15

为什么会这样?

如前所述,每次调用时,您都会获得使用相同时钟初始化的 Random 类的新副本(因此它返回相同的值)。new Random()

现在,从 .NET 6 开始,有一种易于使用且线程安全的替代方案:Random.Shared

在您的示例中,您可以删除所有函数,然后使用以下代码(具有相同的逻辑,但现在它可以正常工作):RandomNumber

byte[] mac = new byte[6];
for (int x = 0; x < 6; ++x)
    mac[x] = (byte)(Random.Shared.Next(0, 255));