Générateur de nombres aléatoires ne générant qu'un seul nombre aléatoire

766

J'ai la fonction suivante:

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

Comment je l'appelle:

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

Si j'étape cette boucle avec le débogueur pendant l'exécution, j'obtiens des valeurs différentes (ce que je veux). Cependant, si je mets un point d'arrêt deux lignes en dessous de ce code, tous les membres du mactableau ont la même valeur.

Pourquoi cela se produit-il?

Ivan Prodanov
la source
21
l'utilisation new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);ne donne pas de meilleurs nombres "aléatoires" que.Next(0, 256)
bohdan_trotsenko
1
Vous pouvez trouver ce package NuGet utile. Il fournit une Rand.Next(int, int)méthode statique qui fournit un accès statique à des valeurs aléatoires sans se bloquer ni rencontrer le problème de réutilisation de la graine
ChaseMedallion

Réponses:

1043

Chaque fois que vous le faites, new Random()il est initialisé à l'aide de l'horloge. Cela signifie que dans une boucle serrée, vous obtenez souvent la même valeur. Vous devez conserver une seule instance aléatoire et continuer à utiliser Next sur la même instance.

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

Edit (voir commentaires): pourquoi avons-nous besoin d'un lockici?

Fondamentalement, Nextva changer l'état interne de l' Randominstance. Si nous le faisons en même temps à partir de plusieurs threads, vous pourriez dire "nous venons de rendre le résultat encore plus aléatoire", mais ce que nous faisons en fait est potentiellement de casser l'implémentation interne, et nous pourrions également commencer à obtenir les mêmes chiffres à partir de différents threads, ce qui pourrait être un problème - et peut-être pas. La garantie de ce qui se passe en interne est le plus gros problème, cependant; car Randomne pas faire aucune garantie de fil de sécurité. Il existe donc deux approches valides:

  • Synchronisez afin de ne pas y accéder en même temps à partir de différents threads
  • Utiliser différentes Randominstances par thread

Soit peut être bien; mais mutexer une seule instance de plusieurs appelants en même temps ne fait que demander des ennuis.

Le lockréalise la première (et la plus simple) de ces approches; cependant, une autre approche pourrait être:

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

c'est alors par thread, vous n'avez donc pas besoin de synchroniser.

Marc Gravell
la source
19
En règle générale, toutes les méthodes statiques doivent être sécurisées pour les threads, car il est difficile de garantir que plusieurs threads ne l'appelleront pas en même temps. Il n'est généralement pas nécessaire de rendre les méthodes d' instance (c'est-à-dire non statiques) thread-safe.
Marc Gravell
5
@Florin - il n'y a pas de différence concernant le "stack based" entre les deux. Les champs statiques sont tout autant «état externe», et seront absolument partagés entre les appelants. Avec les instances, il y a de fortes chances que différents threads aient des instances différentes (un modèle commun). Avec la statique, il est garanti qu'ils partagent tous (sans compter [ThreadStatic]).
Marc Gravell
2
@gdoron obtenez-vous une erreur? Le "verrou" devrait empêcher les fils de trébucher les uns sur les autres ici ...
Marc Gravell
6
@Dan si l'objet n'est jamais exposé publiquement: vous le pouvez. Le risque (très théorique) est qu'un autre thread se verrouille dessus comme vous ne vous y attendiez pas.
Marc Gravell
3
@smiron Il est très probable que vous utilisiez simplement l'extérieur aléatoire d'un verrou. Le verrouillage n'empêche pas tout accès à ce que vous verrouillez - il s'assure simplement que deux instructions de verrouillage sur la même instance ne s'exécuteront pas simultanément. Donc, lock (syncObject)cela n'aidera que si tous les random.Next() appels sont également à l'intérieur lock (syncObject). Si le scénario que vous décrivez se produit même avec une lockutilisation correcte , il se produit également très probablement dans un scénario à un seul thread (par exemple, il Randomest subtilement cassé).
Luaan
118

Pour faciliter la réutilisation dans toute votre application, une classe statique peut vous aider.

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

Vous pouvez utiliser puis utiliser une instance aléatoire statique avec du code tel que

StaticRandom.Instance.Next(1, 100);
Phil
la source
62

La solution de Mark peut être assez coûteuse car elle doit être synchronisée à chaque fois.

Nous pouvons contourner le besoin de synchronisation en utilisant le modèle de stockage spécifique au thread:


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

Mesurez les deux implémentations et vous devriez voir une différence significative.

Hans Malherbe
la source
12
Les serrures sont très bon marché quand elles ne sont pas contestées ... et même si elles sont contestées, je m'attendrais à ce que le code "maintenant faire quelque chose avec le numéro" éclipse le coût de la serrure dans les scénarios les plus intéressants.
Marc Gravell
4
D'accord, cela résout le problème de verrouillage, mais n'est-ce pas encore une solution très compliquée à un problème trivial: vous devez écrire `` deux '' lignes de code pour générer un nombre aléatoire au lieu d'une. Cela vaut-il vraiment la peine d'économiser la lecture d'une simple ligne de code?
EMP
4
+1 Utiliser une Randominstance globale supplémentaire pour obtenir la graine est une bonne idée. Notez également que le code peut être encore simplifié en utilisant la ThreadLocal<T>classe introduite dans .NET 4 (comme Phil l'a également écrit ci-dessous ).
Groo
40

Ma réponse d' ici :

Réitérant simplement la bonne solution :

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

Vous pouvez donc appeler:

var i = Util.GetRandom();

tout au long de.

Si vous avez strictement besoin d'une véritable méthode statique sans état pour générer des nombres aléatoires, vous pouvez vous fier à a Guid.

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

Ça va être un peu plus lent, mais peut être beaucoup plus aléatoire que Random.Next, du moins d'après mon expérience.

Mais pas :

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

La création inutile d'objets va le ralentir, surtout sous une boucle.

Et jamais :

new Random().Next();

Non seulement c'est plus lent (à l'intérieur d'une boucle), son caractère aléatoire est ... enfin pas vraiment bon selon moi ..

nawfal
la source
10
Je ne suis pas d'accord avec l'affaire Guid. La classe Random implémente une distribution uniforme. Ce qui n'est pas le cas dans Guid. Le but de Guid est d'être unique et non uniformément distribué (et sa mise en œuvre est la plupart du temps basée sur une propriété matérielle / machine qui est l'opposé de ... le hasard).
Askolein
4
si vous ne pouvez pas prouver l'uniformité de la génération de Guid, alors il est faux de l'utiliser comme aléatoire (et le hachage serait un autre pas loin de l'uniformité). De même, les collisions ne sont pas un problème: l'uniformité de la collision l'est. Concernant la génération Guid n'étant plus sur le matériel je vais à RTFM, ma mauvaise (toute référence?)
Askolein
5
Il y a deux interprétations de "aléatoire": 1. manque de modèle ou 2. manque de modèle suite à une évolution décrite par une distribution de probabilité (2 inclus dans 1). Votre exemple Guid est correct dans le cas 1, pas dans le cas 2. En face: la Randomclasse correspond au cas 2 (donc, le cas 1 aussi). Vous ne pouvez remplacer l'utilisation de Randompar votre que Guid+Hashsi vous n'êtes pas dans le cas 2. Le cas 1 est probablement suffisant pour répondre à la question, puis, tout Guid+Hashfonctionne bien. Mais ce n'est pas dit clairement (ps: cet uniforme )
Askolein
2
@Askolein Juste pour quelques données de test, je lance plusieurs lots des deux Randomet Guid.NewGuid().GetHashCode()via Ent ( fourmilab.ch/random ) et les deux sont de même aléatoires. new Random(Guid.NewGuid().GetHashCode())fonctionne tout aussi bien, tout comme l'utilisation d'un "maître" synchronisé Randompour générer des graines pour les "enfants" Random. Bien sûr, cela dépend de la façon dont votre système génère des guides - pour mon système, ils sont assez aléatoires, et pour d'autres, cela peut même être crypto-aléatoire. Windows ou MS SQL semble donc bien de nos jours. Mono et / ou mobile peuvent cependant être différents.
Luaan
2
@EdB Comme je l'ai dit dans les commentaires précédents, alors que Guid (un grand nombre) est censé être unique, GetHashCodele Guid dans .NET est dérivé de sa représentation sous forme de chaîne. La sortie est assez aléatoire à mon goût.
nawfal
27

Je préfère utiliser la classe suivante pour générer des nombres aléatoires:

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);
en être loin
la source
30
Je ne suis pas un des votants, mais notez que le PNRG standard répond à un véritable besoin - c'est-à-dire être capable de reproduire de manière répétitive une séquence à partir d'une graine connue. Parfois, le simple coût d'un véritable RNG cryptographique est trop élevé. Et parfois, un RNG crypto est nécessaire. Chevaux pour les cours, pour ainsi dire.
Marc Gravell
4
Selon la documentation, cette classe est thread-safe, donc c'est quelque chose en sa faveur.
Rob Church
Quelle est la probabilité que deux chaînes aléatoires soient identiques en utilisant cela? Si la chaîne ne comporte que 3 caractères, je suppose que cela se produira avec une forte probabilité, mais qu'en est-il de 255 caractères, est-il possible d'avoir la même chaîne aléatoire ou est garanti que cela ne peut pas se produire à partir de l'algorithme?
Lyubomir Velchev
16

1) Comme l'a dit Marc Gravell, essayez d'utiliser UN générateur aléatoire. C'est toujours cool d'ajouter cela au constructeur: System.Environment.TickCount.

2) Un conseil. Supposons que vous vouliez créer 100 objets et supposons que chacun d'eux ait son propre générateur aléatoire (pratique si vous calculez les CHARGES de nombres aléatoires en très peu de temps). Si vous le faisiez en boucle (génération de 100 objets), vous pourriez faire ça comme ça (pour assurer un caractère totalement aléatoire):

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

À votre santé.

sabiland
la source
3
Je déplacerais System.Environment.TickCount hors de la boucle. Si elle se termine pendant que vous itérez, vous aurez deux éléments initialisés à la même graine. Une autre option serait de combiner le tickcount an i différemment (par exemple, System.Environment.TickCount << 8 + i)
Dolphin
Si je comprends bien: voulez-vous dire, cela pourrait arriver, que "System.Environment.TickCount + i" pourrait entraîner la même valeur?
sabiland le
EDIT: Bien sûr, pas besoin d'avoir TickCount dans la boucle. Ma faute :).
sabiland le
2
Le Random()constructeur par défaut appelle Random(Environment.TickCount)quand même
Alsty
5

Chaque fois que vous exécutez

Random random = new Random (15);

Peu importe que vous l'exécutiez des millions de fois, vous utiliserez toujours la même graine.

Si tu utilises

Random random = new Random ();

Vous obtenez une séquence de nombres aléatoires différente, si un pirate devine la graine et que votre algorithme est lié à la sécurité de votre système - votre algorithme est cassé. Si vous exécutez mult. Dans ce constructeur, la graine est spécifiée par l'horloge système et si plusieurs instances sont créées en très peu de temps (millisecondes), il est possible qu'elles aient la même graine.

Si vous avez besoin de nombres aléatoires sûrs, vous devez utiliser la classe

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

Usage:

int randomNumber = Next(1,100);
Joma
la source
It does not matter if you execute it millions of times, you will always use the same seed. Ce n'est pas vrai, sauf si vous spécifiez la graine vous-même.
LarsTech
Réparé. Merci exactement comme vous le dites LarsTech, si la même graine est toujours spécifiée, la même séquence de nombres aléatoires sera toujours générée. Dans ma réponse, je me réfère au constructeur avec des paramètres si vous utilisez toujours la même graine. La classe Random génère uniquement des nombres pseudo aléatoires. Si quelqu'un découvre quelle graine vous avez utilisée dans votre algorithme, cela peut compromettre la sécurité ou le caractère aléatoire de votre algorithme. Avec la classe RNGCryptoServiceProvider, vous pouvez avoir en toute sécurité des nombres aléatoires. J'ai déjà corrigé, merci beaucoup pour la correction.
Joma
0

déclarez simplement la variable de classe aléatoire comme ceci:

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

si vous voulez obtenir un nombre aléatoire différent à chaque fois dans votre liste, utilisez

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

Chaque fois en déclarant Random r = new Random()une fois.

Abdul
la source
Lorsque vous l'appelez, new Random()il utilise l'horloge système, mais si vous appelez l'intégralité de ce code deux fois de suite avant que l'horloge ne change, vous obtiendrez le même nombre aléatoire. C'est tout l'intérêt des réponses ci-dessus.
Savage
-1

Il y a beaucoup de solutions, en voici une: si vous voulez seulement effacer les lettres et que la méthode reçoit un aléatoire et la longueur du résultat.

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);
}
Marztres
la source
Le problème de base est toujours le même - vous passez dans une Randominstance, mais vous vous attendez toujours à ce que l'appelant crée une instance partagée. Si l'appelant crée une nouvelle instance à chaque fois et que le code est exécuté deux fois avant le changement d'horloge, vous obtiendrez le même nombre aléatoire. Cette réponse émet donc toujours des hypothèses qui pourraient être erronées.
Savage
En outre, l'intérêt d'avoir une méthode pour générer des nombres aléatoires est l'encapsulation - que la méthode appelante n'a pas à se soucier de l'implémentation, elle ne souhaite que récupérer un nombre aléatoire
Savage