Gerador de número aleatório gerando apenas um número aleatório


765

Eu tenho a seguinte função:

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

Como eu chamo isso:

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

Se eu passo esse loop com o depurador durante o tempo de execução, recebo valores diferentes (que é o que eu quero). No entanto, se eu colocar um ponto de interrupção duas linhas abaixo desse código, todos os membros da macmatriz terão valor igual.

Por que isso acontece?


20
usando new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);não deu qualquer melhores números "aleatórios" que.Next(0, 256)
bohdan_trotsenko

Você pode encontrar este pacote NuGet útil. Ele fornece um estático Rand.Next(int, int)método que dá acesso estática para valores aleatórios sem bloquear ou que correm para o problema reutilização de sementes
ChaseMedallion

Respostas:


1042

Toda vez que você faz new Random()isso é inicializado usando o relógio. Isso significa que, em um circuito fechado, você obtém o mesmo valor várias vezes. Você deve manter uma única instância aleatória e continuar usando Next na mesma instância.

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

Editar (ver comentários): por que precisamos de um lockaqui?

Basicamente, Nextvai mudar o estado interno da Randominstância. Se fizermos isso ao mesmo tempo a partir de vários encadeamentos, você poderá argumentar "acabamos de tornar o resultado ainda mais aleatório", mas o que realmente estamos fazendo é potencialmente violar a implementação interna e também podemos começar a obter os mesmos números de threads diferentes, o que pode ser um problema - e talvez não. A garantia do que acontece internamente é o problema maior; desde Randomque não faça nenhuma garantia de segurança de linha. Portanto, existem duas abordagens válidas:

  • Sincronize para não acessá-lo ao mesmo tempo a partir de threads diferentes
  • Use Randominstâncias diferentes por thread

Qualquer um pode ficar bem; mas mutex uma instância única de vários chamadores ao mesmo tempo está apenas causando problemas.

O lockatinge a primeira (e mais simples) dessas abordagens; no entanto, outra abordagem pode ser:

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

isso é por thread, para que você não precise sincronizar.


19
Como regra geral, todos os métodos estáticos devem ser protegidos contra thread, pois é difícil garantir que vários threads não o chamem ao mesmo tempo. Geralmente, não é necessário tornar os métodos de instância (ou seja, não estáticos) seguros para threads.
Marc Gravell

5
@ Florin - não há diferença re "pilha com base" entre os dois. Os campos estáticos têm o mesmo "estado externo" e serão absolutamente compartilhados entre os chamadores. Com instâncias, há uma boa chance de que diferentes threads tenham instâncias diferentes (um padrão comum). Com a estática, é garantido que todos eles compartilhem (não incluindo [ThreadStatic]).
Marc Gravell

2
@gdoron você está recebendo um erro? A "trava" deve impedir que os fios tropeçam uns nos outros aqui ...
Marc Gravell

6
@ Dan, se o objeto nunca for exposto publicamente: você pode. O risco (muito teórico) é que algum outro segmento esteja bloqueando-o de maneiras que você não esperava.
Marc Gravell

3
@smiron É muito provável que você também esteja usando o aleatório fora de uma fechadura. O bloqueio não impede todo o acesso ao que você está bloqueando - apenas garante que duas instruções de bloqueio na mesma instância não sejam executadas simultaneamente. Isso lock (syncObject)só ajudará se todas as random.Next() chamadas também estiverem dentro lock (syncObject). Se o cenário que você descreve ocorrer mesmo com o lockuso correto , também é extremamente provável que ocorra em um cenário de thread único (por exemplo, Randomestá sutilmente quebrado).
Luaan

118

Para facilitar a reutilização em todo o aplicativo, uma classe estática pode ajudar.

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

Você pode usar e usar instância aleatória estática com código como

StaticRandom.Instance.Next(1, 100);

62

A solução de Mark pode ser bastante cara, pois precisa ser sincronizada sempre.

Podemos contornar a necessidade de sincronização usando o padrão de armazenamento específico do encadeamento:


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

Meça as duas implementações e você verá uma diferença significativa.


12
Os bloqueios são muito baratos quando não são contestados ... e, mesmo se contestados, eu esperaria que o código "agora faça algo com o número" diminua o custo do bloqueio nos cenários mais interessantes.
Marc Gravell

4
De acordo, isso resolve o problema de bloqueio, mas ainda não é uma solução altamente complicada para um problema trivial: você precisa escrever '' duas '' linhas de código para gerar um número aleatório em vez de um. Vale a pena salvar a leitura de uma simples linha de código?
EMP

4
+1 Usar uma Randominstância global adicional para obter a semente é uma boa ideia. Observe também que o código pode ser simplificado ainda mais usando a ThreadLocal<T>classe introduzida no .NET 4 (como Phil também escreveu abaixo ).
Groo

40

Minha resposta daqui :

Apenas reiterando a solução certa :

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

Então você pode ligar para:

var i = Util.GetRandom();

em toda parte.

Se você precisar estritamente de um método estático verdadeiro sem estado para gerar números aleatórios, poderá confiar em a Guid.

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

Vai ser um pouco mais lento, mas pode ser muito mais aleatório do que Random.Next, pelo menos da minha experiência.

Mas não :

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

A criação desnecessária de objetos vai torná-lo mais lento, especialmente em um loop.

E nunca :

new Random().Next();

Não só é mais lento (dentro de um loop), sua aleatoriedade é ... bem, não é muito boa de acordo comigo.


10
Não concordo com o caso Guid. A classe Random implementa uma distribuição uniforme. O que não é o caso em Guid. O objetivo da Guid é ser único e não distribuir uniformemente (e sua implementação é na maioria das vezes baseada em algumas propriedades de hardware / máquina que são o oposto de ... aleatoriedade).
Askolein 31/03

4
se você não pode provar a uniformidade da geração Guid, é errado usá-la aleatoriamente (e o Hash estaria a outro passo da uniformidade). Da mesma forma, colisões não são um problema: a uniformidade da colisão é. Em relação à geração Guid não ser em hardware mais vou RTFM, meu mau (qualquer referência?)
Askolein

5
Há dois entendimentos de "Aleatório": 1. falta de padrão ou 2. falta de padrão após uma evolução descrita por uma distribuição de probabilidade (2 incluídas em 1). Seu exemplo do Guid está correto no caso 1, não no caso 2. Ao contrário: a Randomclasse corresponde ao caso 2 (portanto, também ao caso 1). Você só pode substituir o uso Randompelo seu Guid+Hashse você não estiver no caso 2. O caso 1 provavelmente é suficiente para responder à pergunta e, em seguida, o seu Guid+Hashtrabalho está correto. Mas isso não é dito claramente (ps: esse uniforme ) #
315 Askolein

2
@Askolein Apenas para alguns dados de teste, eu executo vários lotes de ambos Randome Guid.NewGuid().GetHashCode()através do Ent ( fourmilab.ch/random ) e ambos são igualmente aleatórios. new Random(Guid.NewGuid().GetHashCode())funciona tão bem quanto o uso de um "mestre" sincronizado Randompara gerar sementes para "filhos" Random. É claro que depende de como o seu sistema gera Guids - para o meu sistema, eles são bastante aleatórios e, em outros, pode até ser cripto-aleatório. Então, o Windows ou o MS SQL parecem bons hoje em dia. Mono e / ou celular podem ser diferentes, no entanto.
Luaan

2
@ EdB Como eu disse nos comentários anteriormente, enquanto o Guid (um grande número) é único, GetHashCodeo Guid no .NET é derivado de sua representação em cadeia. A saída é bastante aleatória para o meu gosto.
Nawfal 3/15

27

Prefiro usar a seguinte classe para gerar números aleatórios:

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

30
Eu não sou um dos menos votantes, mas observe que o PNRG padrão atende a uma necessidade genuína - ou seja, ser capaz de reproduzir repetidamente uma sequência de uma semente conhecida. Às vezes, o simples custo de um verdadeiro RNG criptográfico é muito alto. E, às vezes, é necessário um RNG de criptografia. Cavalos para cursos, por assim dizer.
Marc Gravell

4
De acordo com a documentação, esta classe é segura para threads, então isso é algo a seu favor.
Rob Church

Qual é a probabilidade de duas seqüências aleatórias serem uma e a mesma usando isso? Se a string tiver apenas 3 caracteres, acho que isso acontecerá com alta probabilidade, mas e se o comprimento de 255 caracteres for possível ter a mesma string aleatória ou garantir que isso não ocorra a partir do algoritmo?
Lyubomir Velchev

16

1) Como Marc Gravell disse, tente usar UM gerador aleatório. É sempre legal adicionar isso ao construtor: System.Environment.TickCount.

2) Uma dica. Digamos que você queira criar 100 objetos e suponha que cada um deles tenha seu próprio gerador aleatório (útil se você calcular LOADS de números aleatórios em um período muito curto de tempo). Se você fizer isso em um loop (geração de 100 objetos), poderá fazer o seguinte (para garantir a aleatoriedade total):

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

Felicidades.


3
Eu moveria System.Environment.TickCount para fora do loop. Se o tempo passar durante a iteração, você terá dois itens inicializados na mesma semente. Outra opção seria a de combinar o TickCount um i de forma diferente (por exemplo System.Environment.TickCount << 8 + i)
Dolphin

Se bem entendi: quer dizer, pode acontecer que "System.Environment.TickCount + i" possa resultar no mesmo valor?
sabiland

EDIT: Claro, não há necessidade de TickCount dentro do loop. Foi mal :).
sabiland

2
O Random()construtor padrão chama Random(Environment.TickCount)assim mesmo
Alsty 16/02

5

Toda vez que você executa

Random random = new Random (15);

Não importa se você o executar milhões de vezes, sempre usará a mesma semente.

Se você usar

Random random = new Random ();

Você obtém uma sequência numérica aleatória diferente, se um hacker adivinhar a semente e seu algoritmo estiver relacionado à segurança do seu sistema - seu algoritmo está quebrado. Eu você executa mult. Nesse construtor, a semente é especificada pelo relógio do sistema e, se várias instâncias são criadas em um período muito curto de tempo (milissegundos), é possível que elas tenham a mesma semente.

Se você precisar de números aleatórios seguros, deverá usar a 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);
}

Uso:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. Isso não é verdade, a menos que você mesmo especifique a semente.
LarsTech

Consertado. Obrigado Exatamente como você diz LarsTech, se a mesma semente for sempre especificada, a mesma sequência de números aleatórios sempre será gerada. Na minha resposta, refiro-me ao construtor com parâmetros se você sempre usa a mesma semente. A classe Random gera apenas números pseudo-aleatórios. Se alguém descobrir qual semente você usou no seu algoritmo, isso poderá comprometer a segurança ou a aleatoriedade do seu algoritmo. Com a classe RNGCryptoServiceProvider, você pode ter números aleatórios com segurança. Eu já corrigi, muito obrigado pela correção.
Joma

0

Apenas declare a variável da classe Random assim:

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

se você deseja obter um número aleatório diferente de cada vez da sua lista, use

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

Cada vez, declarando Random r = new Random()uma vez.


Quando você liga para new Random()ele, usa o relógio do sistema, mas se você chamar esse código inteiro duas vezes seguidas antes que o relógio mude, você obterá o mesmo número aleatório. Esse é o objetivo das respostas acima.
Savage

-1

Existem muitas soluções, eis uma: se você deseja que apenas o número apague as letras e o método receba aleatoriamente e o comprimento do resultado.

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

O problema básico ainda é o mesmo - você está passando em uma Randominstância, mas ainda espera que o chamador crie uma instância compartilhada. Se o chamador criar uma nova instância a cada vez e o código for executado duas vezes antes do relógio mudar, você obterá o mesmo número aleatório. Portanto, essa resposta ainda faz suposições que podem ser errôneas.
Savage

Além disso, toda a ponto de ter um método para gerar números aleatórios é encapsulamento - que o método de chamada não tem que se preocupar com a implementação, está apenas interessado em obter um número aleatório de volta
Savage
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.