Penghasil bilangan acak hanya menghasilkan satu bilangan acak


765

Saya memiliki fungsi berikut:

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

Bagaimana saya menyebutnya:

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

Jika saya langkah loop itu dengan debugger selama runtime saya mendapatkan nilai yang berbeda (yang saya inginkan). Namun, jika saya meletakkan breakpoint dua baris di bawah kode itu, semua anggota macarray memiliki nilai yang sama.

Mengapa itu terjadi?


20
menggunakan new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);tidak menghasilkan angka "acak" yang lebih baik dari.Next(0, 256)
bohdan_trotsenko

Anda dapat menemukan paket NuGet ini bermanfaat. Ini menyediakan Rand.Next(int, int)metode statis yang menyediakan akses statis ke nilai acak tanpa mengunci atau mengalami masalah penggunaan kembali unggulan
ChaseMedallion

Jawaban:


1042

Setiap kali Anda melakukannya new Random()diinisialisasi menggunakan jam. Ini berarti bahwa dalam loop ketat Anda mendapatkan nilai yang sama banyak kali. Anda harus menyimpan satu instance Acak dan terus menggunakan Next pada instance yang sama .

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

Sunting (lihat komentar): mengapa kita perlu di locksini?

Pada dasarnya, Nextakan mengubah keadaan internal Randominstance. Jika kami melakukan itu secara bersamaan dari beberapa utas, Anda dapat berdebat "kami baru saja membuat hasilnya lebih acak", tetapi apa yang sebenarnya kami lakukan berpotensi melanggar implementasi internal, dan kami juga bisa mulai mendapatkan angka yang sama dari utas berbeda, yang mungkin menjadi masalah - dan mungkin tidak. Namun, jaminan apa yang terjadi secara internal adalah masalah yang lebih besar; karena Randomtidak tidak membuat jaminan benang-keselamatan. Jadi ada dua pendekatan yang valid:

  • Sinkronisasi agar kami tidak mengaksesnya secara bersamaan dari utas yang berbeda
  • Gunakan Randomcontoh berbeda per utas

Baik bisa baik-baik saja; tetapi mutexing satu contoh dari beberapa penelepon pada saat yang sama hanya meminta masalah.

The lockmencapai pertama (dan sederhana) dari pendekatan ini; Namun, pendekatan lain mungkin:

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

ini kemudian per-utas, jadi Anda tidak perlu melakukan sinkronisasi.


19
Sebagai aturan umum, semua metode statis harus dibuat aman-utas, karena sulit untuk memastikan bahwa beberapa utas tidak akan memanggilnya secara bersamaan. Biasanya tidak diperlukan untuk membuat metode instan (yaitu non-statis) aman.
Marc Gravell

5
@Florin - tidak ada perbedaan "tumpukan berbasis" antara keduanya. Bidang statis sama banyaknya dengan "kondisi eksternal", dan benar - benar akan dibagikan di antara para penelepon. Dengan instance, ada kemungkinan besar bahwa utas yang berbeda memiliki instance yang berbeda (pola umum). Dengan statika, dijamin semuanya berbagi (tidak termasuk [ThreadStatic]).
Marc Gravell

2
@ gdoron apakah Anda mendapatkan kesalahan? "Kunci" harus mencegah benang saling bertabrakan di sini ...
Marc Gravell

6
@Bisa jika objek tidak pernah diekspos secara publik: Anda bisa. Risiko (sangat teoritis) adalah bahwa utas lainnya menguncinya dengan cara yang tidak Anda harapkan.
Marc Gravell

3
@smiron Kemungkinan besar Anda hanya menggunakan kunci acak di luar. Mengunci tidak mencegah semua akses ke apa yang Anda kunci - hanya memastikan bahwa dua pernyataan kunci pada contoh yang sama tidak akan berjalan secara bersamaan. Jadi lock (syncObject)hanya akan membantu jika semua random.Next() panggilan juga ada di dalam lock (syncObject). Jika skenario yang Anda gambarkan benar-benar terjadi bahkan dengan lockpenggunaan yang benar , itu juga sangat mungkin terjadi dalam skenario single-threaded (mis. RandomRusak secara halus).
Luaan

118

Untuk kemudahan penggunaan ulang di seluruh aplikasi Anda, sebuah kelas statis dapat membantu.

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

Anda dapat menggunakan lalu menggunakan contoh acak statis dengan kode seperti

StaticRandom.Instance.Next(1, 100);

62

Solusi Mark bisa sangat mahal karena perlu melakukan sinkronisasi setiap kali.

Kita dapat menyiasati kebutuhan untuk sinkronisasi dengan menggunakan pola penyimpanan khusus 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);
    }
}

Ukur kedua implementasi dan Anda akan melihat perbedaan yang signifikan.


12
Kunci sangat murah ketika mereka tidak diperebutkan ... dan bahkan jika diperebutkan saya akan mengharapkan kode "sekarang lakukan sesuatu dengan nomor" untuk mengurangi biaya kunci dalam skenario paling menarik.
Marc Gravell

4
Setuju, ini menyelesaikan masalah penguncian, tetapi bukankah ini masih solusi yang sangat rumit untuk masalah sepele: bahwa Anda perlu menulis "dua" baris kode untuk menghasilkan angka acak, bukan satu. Apakah ini benar-benar layak untuk menghemat membaca satu baris kode sederhana?
EMP

4
+1 Menggunakan Randomcontoh global tambahan untuk mendapatkan benih adalah ide bagus. Perhatikan juga bahwa kode dapat lebih disederhanakan menggunakan ThreadLocal<T>kelas yang diperkenalkan di .NET 4 (seperti yang ditulis Phil di bawah ).
Groo

40

Jawaban saya dari sini :

Hanya mengulangi solusi yang tepat :

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

Jadi, Anda dapat menelepon:

var i = Util.GetRandom();

semua di seluruh.

Jika Anda benar-benar membutuhkan metode statis stateless sejati untuk menghasilkan angka acak, Anda bisa mengandalkan a Guid.

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

Ini akan menjadi sedikit lebih lambat, tetapi bisa jauh lebih acak daripada Random.Next, setidaknya dari pengalaman saya.

Tapi tidak :

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

Pembuatan objek yang tidak perlu akan membuatnya lebih lambat terutama di bawah satu lingkaran.

Dan tidak pernah :

new Random().Next();

Tidak hanya lebih lambat (di dalam satu lingkaran), keacakannya adalah ... yah tidak benar-benar bagus menurut saya ..


10
Saya tidak setuju dengan kasus Guid. Kelas acak mengimplementasikan distribusi yang seragam. Yang tidak terjadi dalam Guid. Tujuan Guid adalah menjadi unik dan tidak terdistribusi secara merata (dan implementasinya sebagian besar berdasarkan pada beberapa perangkat keras / properti mesin yang merupakan kebalikan dari ... keacakan).
Askolein

4
jika Anda tidak dapat membuktikan keseragaman generasi Guid, maka salah jika menggunakannya secara acak (dan Hash akan menjadi langkah lain dari keseragaman). Demikian juga, tabrakan bukanlah masalah: keseragaman tabrakan adalah. Mengenai generasi Guid yang tidak lagi menggunakan perangkat keras, saya akan menggunakan RTFM, salah saya (referensi apa pun?)
Askolein

5
Ada dua pemahaman tentang "Acak": 1. kurangnya pola atau 2. kurangnya pola mengikuti evolusi yang dijelaskan oleh distribusi probabilitas (2 termasuk dalam 1). Contoh Panduan Anda benar dalam kasus 1, bukan dalam kasus 2. Sebaliknya: Randomkelas cocok dengan kasus 2 (dengan demikian, kasus 1 juga). Anda hanya dapat mengganti penggunaan Randomoleh Anda Guid+Hashjika Anda tidak dalam kasus 2. Kasus 1 mungkin cukup untuk menjawab Pertanyaan, dan kemudian, Guid+Hashpekerjaan Anda baik-baik saja. Tetapi tidak jelas dikatakan (ps: seragam ini )
Askolein

2
@Askolein Hanya untuk beberapa data uji, saya menjalankan beberapa batch keduanya Randomdan Guid.NewGuid().GetHashCode()melalui Ent ( fourmilab.ch/random ) dan keduanya sama-sama acak. new Random(Guid.NewGuid().GetHashCode())berfungsi dengan baik, seperti halnya menggunakan "master" yang disinkronkan Randomuntuk menghasilkan benih untuk "anak" Random. Tentu saja, itu tergantung pada bagaimana sistem Anda menghasilkan Panduan - untuk sistem saya, mereka cukup acak, dan pada orang lain mungkin bahkan menjadi crypto-random. Jadi Windows atau MS SQL tampaknya baik-baik saja saat ini. Mono dan / atau ponsel mungkin berbeda.
Luaan

2
@ EdB Seperti yang telah saya katakan di komentar sebelumnya, sementara Guid (sejumlah besar) dimaksudkan untuk menjadi unik, GetHashCodeGuid dalam. NET berasal dari representasi string-nya. Outputnya cukup acak sesuai dengan keinginan saya.
nawfal

27

Saya lebih suka menggunakan kelas berikut untuk menghasilkan angka acak:

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

30
Saya bukan salah satu pemilih turun, tetapi perhatikan bahwa PNRG standar melayani kebutuhan asli - yaitu untuk dapat mereproduksi urutan dari benih yang dikenal secara berulang. Kadang-kadang biaya semata-mata RNG kriptografi benar terlalu banyak. Dan terkadang crypto RNG diperlukan. Kuda untuk kursus, jadi untuk berbicara.
Marc Gravell

4
Menurut dokumentasi , kelas ini aman untuk thread, jadi ada baiknya.
Rob Church

Berapa probabilitas dua string acak menjadi satu dan sama menggunakan itu? Jika string hanya 3 karakter, saya kira ini akan terjadi dengan probabilitas tinggi tetapi bagaimana jika panjangnya 255 karakter apakah mungkin untuk memiliki string acak yang sama atau dijamin bahwa ini tidak dapat terjadi dari algoritma?
Lyubomir Velchev

16

1) Seperti yang dikatakan Marc Gravell, coba gunakan ONE-generator acak. Itu selalu keren untuk menambahkan ini ke konstruktor: System.Environment.TickCount.

2) Satu tip. Katakanlah Anda ingin membuat 100 objek dan anggap masing-masing dari mereka harus memiliki generator acak sendiri (berguna jika Anda menghitung LOADS angka acak dalam waktu yang sangat singkat). Jika Anda melakukan ini dalam satu lingkaran (generasi 100 objek), Anda bisa melakukan ini seperti itu (untuk memastikan keacakan sepenuhnya):

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

Bersulang.


3
Saya akan memindahkan System.Environment.TickCount keluar dari loop. Jika terus berdetak saat Anda mengulangi, maka Anda akan memiliki dua item diinisialisasi ke benih yang sama. Pilihan lain adalah menggabungkan tickcount dan i secara berbeda (mis. System.Environment.TickCount << 8 + i)
Dolphin

Jika saya mengerti dengan benar: maksud Anda, bisa saja terjadi, bahwa "System.Environment.TickCount + i" dapat menghasilkan nilai SAMA?
sabiland

Sunting: Tentu saja, tidak perlu memiliki TickCount di dalam loop. Salahku :).
sabiland

2
Default Random()constructor panggilan Random(Environment.TickCount)pula
Alsty

5

Setiap kali Anda mengeksekusi

Random random = new Random (15);

Tidak masalah jika Anda mengeksekusinya jutaan kali, Anda akan selalu menggunakan seed yang sama.

Jika Anda menggunakan

Random random = new Random ();

Anda mendapatkan urutan nomor acak yang berbeda, jika seorang hacker menebak benih dan algoritma Anda terkait dengan keamanan sistem Anda - algoritma Anda rusak. Saya Anda menjalankan mult. Dalam konstruktor ini, seed ditentukan oleh jam sistem dan jika beberapa instance dibuat dalam periode waktu yang sangat singkat (milidetik), mungkin saja mereka memiliki seed yang sama.

Jika Anda membutuhkan nomor acak yang aman, Anda harus menggunakan kelas

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

Pemakaian:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. Itu tidak benar kecuali jika Anda menentukan sendiri benihnya.
LarsTech

Memperbaiki. Terima kasih Persis seperti yang Anda katakan LarsTech, jika seed yang sama selalu ditentukan, urutan angka acak yang sama akan selalu dihasilkan. Dalam jawaban saya, saya merujuk ke konstruktor dengan parameter jika Anda selalu menggunakan seed yang sama. Kelas acak hanya menghasilkan angka acak semu. Jika seseorang mengetahui seed apa yang telah Anda gunakan dalam algoritma Anda, itu dapat membahayakan keamanan atau keacakan algoritma Anda. Dengan kelas RNGCryptoServiceProvider, Anda dapat dengan aman memiliki nomor acak. Saya sudah mengoreksi, terima kasih banyak untuk koreksi.
Joma

0

cukup deklarasikan variabel kelas acak seperti ini:

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

jika Anda ingin mendapatkan nomor acak yang berbeda setiap kali dari daftar Anda maka gunakan

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

Setiap kali dengan mendeklarasikan Random r = new Random()sekali.


Saat Anda memanggilnya new Random()menggunakan jam sistem, tetapi jika Anda memanggil seluruh kode itu dua kali berturut-turut sebelum jam berubah, Anda akan mendapatkan nomor acak yang sama. Itulah inti dari jawaban di atas.
Savage

-1

Ada banyak solusi, di sini satu: jika Anda hanya ingin menghapus nomor surat dan metode menerima secara acak dan panjang hasilnya.

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

Masalah dasarnya masih sama - Anda memberikan Randomcontoh, tetapi Anda masih mengharapkan penelepon untuk membuat contoh bersama. Jika pemanggil membuat instance baru setiap kali dan kode dieksekusi dua kali sebelum jam berubah, Anda akan mendapatkan nomor acak yang sama. Jadi jawaban ini masih membuat asumsi yang bisa salah.
Savage

Juga, inti dari memiliki metode untuk menghasilkan angka acak adalah enkapsulasi - bahwa metode panggilan tidak perlu khawatir tentang implementasi, itu hanya tertarik untuk mendapatkan nomor acak kembali
Savage
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.