Trình tạo số ngẫu nhiên chỉ tạo một số ngẫu nhiên


765

Tôi có chức năng sau:

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

Làm thế nào tôi gọi nó:

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

Nếu tôi bước vòng lặp đó với trình gỡ lỗi trong thời gian chạy, tôi nhận được các giá trị khác nhau (đó là những gì tôi muốn). Tuy nhiên, nếu tôi đặt một điểm dừng hai dòng bên dưới mã đó, tất cả các thành viên của macmảng có giá trị bằng nhau.

Tại sao điều đó xảy ra?


20
sử dụng new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);không mang lại bất kỳ số "ngẫu nhiên" nào tốt hơn.Next(0, 256)
bohdan_trotsenko 18/03/2016

Bạn có thể thấy gói NuGet này hữu ích. Nó cung cấp một Rand.Next(int, int)phương thức tĩnh cung cấp quyền truy cập tĩnh vào các giá trị ngẫu nhiên mà không bị khóa hoặc chạy vào vấn đề sử dụng lại hạt giống
ChaseMedallion

Câu trả lời:


1042

Mỗi khi bạn làm điều new Random()đó được khởi tạo bằng đồng hồ. Điều này có nghĩa là trong một vòng lặp chặt chẽ, bạn nhận được cùng một giá trị rất nhiều lần. Bạn nên giữ một thể hiện ngẫu nhiên duy nhất và tiếp tục sử dụng Next trên cùng một thể hiện.

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

Chỉnh sửa (xem bình luận): tại sao chúng ta cần một lockở đây?

Về cơ bản, Nextsẽ thay đổi trạng thái nội bộ của Randomthể hiện. Nếu chúng tôi làm điều đó cùng một lúc từ nhiều luồng, bạn có thể lập luận rằng "chúng tôi vừa tạo ra kết quả thậm chí còn ngẫu nhiên hơn", nhưng những gì chúng tôi thực sự đang làm có khả năng phá vỡ việc thực hiện nội bộ và chúng tôi cũng có thể bắt đầu nhận được những con số tương tự từ các chủ đề khác nhau, có thể là một vấn đề - và có thể không. Tuy nhiên, sự đảm bảo cho những gì xảy ra trong nội bộ là vấn đề lớn hơn; kể từ khi Randomnào không thực hiện bất kỳ sự bảo đảm của thread-an toàn. Do đó, có hai cách tiếp cận hợp lệ:

  • Đồng bộ hóa để chúng tôi không truy cập cùng lúc từ các luồng khác nhau
  • Sử dụng các Randomtrường hợp khác nhau cho mỗi luồng

Hoặc là có thể tốt; nhưng việc tạo ra một trường hợp duy nhất từ nhiều người gọi cùng một lúc chỉ là vấn đề.

Việc lockđạt được đầu tiên (và đơn giản hơn) của các phương pháp này; tuy nhiên, một cách tiếp cận khác có thể là:

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

đây là mỗi luồng, vì vậy bạn không cần phải đồng bộ hóa.


19
Theo nguyên tắc chung, tất cả các phương thức tĩnh phải được làm cho luồng an toàn, vì khó có thể đảm bảo rằng nhiều luồng sẽ không gọi nó cùng một lúc. Thông thường không cần thiết phải tạo ra các phương thức cá thể (tức là không tĩnh).
Marc Gravell

5
@Florin - không có sự khác biệt giữa "stack dựa" giữa hai. Các trường tĩnh cũng giống như "trạng thái bên ngoài" và hoàn toàn sẽ được chia sẻ giữa những người gọi. Với các thể hiện, rất có thể các luồng khác nhau có các thể hiện khác nhau (một mẫu chung). Với statics, đảm bảo rằng tất cả họ đều chia sẻ (không bao gồm [ThreadStatic]).
Marc Gravell

2
@gdoron bạn có bị lỗi không? "Khóa" sẽ ngăn các chủ đề vấp ngã nhau ở đây ...
Marc Gravell

6
@Dan nếu đối tượng không bao giờ được công khai: bạn có thể. Rủi ro (rất lý thuyết) là một số luồng khác đang khóa nó theo những cách bạn không mong đợi.
Marc Gravell

3
@smiron Rất có thể bạn chỉ đơn giản là sử dụng ngẫu nhiên bên ngoài khóa. Khóa không ngăn tất cả quyền truy cập vào những gì bạn đang khóa xung quanh - nó chỉ đảm bảo rằng hai câu lệnh khóa trên cùng một ví dụ sẽ không chạy đồng thời. Vì vậy, lock (syncObject)sẽ chỉ giúp nếu tất cả các random.Next() cuộc gọi cũng trong lock (syncObject). Nếu kịch bản bạn mô tả xảy ra ngay cả khi locksử dụng đúng , thì nó cũng rất có thể xảy ra trong một kịch bản đơn luồng (ví dụ: Randombị phá vỡ một cách tinh tế).
Luaan

118

Để dễ sử dụng lại trong toàn bộ ứng dụng của bạn, một lớp tĩnh có thể giúp ích.

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

Bạn có thể sử dụng sau đó sử dụng thể hiện ngẫu nhiên tĩnh với mã như

StaticRandom.Instance.Next(1, 100);

62

Giải pháp của Mark có thể khá tốn kém vì nó cần phải đồng bộ hóa mọi lúc.

Chúng ta có thể giải quyết nhu cầu đồng bộ hóa bằng cách sử dụng mẫu lưu trữ dành riêng cho luồng:


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

Đo lường hai triển khai và bạn sẽ thấy một sự khác biệt đáng kể.


12
Khóa rất rẻ khi chúng không bị tranh cãi ... và ngay cả khi bị tranh cãi, tôi sẽ mong đợi mã "bây giờ làm gì đó với số" để giảm chi phí của khóa trong hầu hết các tình huống thú vị.
Marc Gravell

4
Đồng ý, điều này giải quyết vấn đề khóa, nhưng đây không phải là một giải pháp cực kỳ phức tạp cho một vấn đề tầm thường: bạn cần phải viết các dòng mã '' hai '' để tạo một số ngẫu nhiên thay vì một số. Đây có thực sự là giá trị nó để tiết kiệm đọc một dòng mã đơn giản?
EMP

4
+1 Sử dụng một Randomví dụ toàn cầu bổ sung để nhận hạt giống là một ý tưởng hay. Cũng lưu ý rằng mã có thể được đơn giản hóa hơn nữa bằng cách sử dụng ThreadLocal<T>lớp được giới thiệu trong .NET 4 (như Phil cũng đã viết bên dưới ).
Groo

40

Câu trả lời của tôi từ đây :

Chỉ cần nhắc lại giải pháp đúng :

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

Vì vậy, bạn có thể gọi:

var i = Util.GetRandom();

tất cả đã qua.

Nếu bạn thực sự cần một phương thức tĩnh không trạng thái thực để tạo số ngẫu nhiên, bạn có thể dựa vào a Guid.

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

Nó sẽ chậm hơn một chút, nhưng có thể ngẫu nhiên hơn nhiều Random.Next, ít nhất là từ kinh nghiệm của tôi.

Nhưng không phải :

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

Việc tạo đối tượng không cần thiết sẽ làm cho nó chậm hơn, đặc biệt là trong một vòng lặp.

không bao giờ :

new Random().Next();

Không chỉ chậm hơn (trong vòng lặp), tính ngẫu nhiên của nó là ... cũng không thực sự tốt theo tôi ..


10
Tôi không đồng ý với trường hợp Hướng dẫn. Lớp Random thực hiện phân phối đồng đều. Đó không phải là trường hợp trong Guid. Mục tiêu của Guid là duy nhất không được phân phối đồng đều (và việc thực hiện nó hầu hết thời gian dựa trên một số thuộc tính phần cứng / máy trái ngược với ... tính ngẫu nhiên).
Askolein

4
nếu bạn không thể chứng minh tính đồng nhất của thế hệ Guid, thì việc sử dụng nó là ngẫu nhiên (và Hash sẽ là một bước khác so với tính đồng nhất). Tương tự như vậy, va chạm không phải là một vấn đề: tính đồng nhất của va chạm là. Liên quan đến thế hệ Guid không còn trên phần cứng nữa Tôi sẽ đến RTFM, điều xấu của tôi (có tham khảo gì không?)
Askolein

5
Có hai cách hiểu về "Ngẫu nhiên": 1. thiếu mẫu hoặc 2. thiếu mẫu theo sự tiến hóa được mô tả bởi phân phối xác suất (2 bao gồm trong 1). Ví dụ Hướng dẫn của bạn là chính xác trong trường hợp 1, không phải trong trường hợp 2. Ngược lại: Randomlớp khớp với trường hợp 2 (do đó, trường hợp 1 cũng vậy). Bạn chỉ có thể thay thế việc sử dụng của Randombạn Guid+Hashnếu bạn không ở trong trường hợp 2. Trường hợp 1 có thể đủ để trả lời Câu hỏi, và sau đó, Guid+Hashcông việc của bạn vẫn ổn. Nhưng nó không được nói rõ ràng (ps: đồng phục này )
Askolein

2
@Askolein Chỉ cần một số dữ liệu thử nghiệm, tôi chạy một số đợt của cả hai RandomGuid.NewGuid().GetHashCode()thông qua Ent ( Fourmilab.ch/random ) và cả hai đều ngẫu nhiên như nhau. new Random(Guid.NewGuid().GetHashCode())cũng hoạt động tốt, cũng như sử dụng một "chủ" được đồng bộ hóa Randomđể tạo hạt giống cho "trẻ em" Random. Tất nhiên, nó phụ thuộc vào cách hệ thống của bạn tạo Hướng dẫn - đối với hệ thống của tôi, chúng khá ngẫu nhiên và trên các hệ thống khác có thể thậm chí là tiền điện tử ngẫu nhiên. Vì vậy, Windows hoặc MS SQL có vẻ tốt hiện nay. Mono và / hoặc di động có thể khác nhau, mặc dù.
Luaan

2
@EdB Như tôi đã nói trong các bình luận trước đây, trong khi Guid (một số lượng lớn) có nghĩa là duy nhất, GetHashCodethì Guid trong .NET có nguồn gốc từ biểu diễn chuỗi của nó. Đầu ra khá ngẫu nhiên theo ý thích của tôi.
nawfal

27

Tôi thà sử dụng lớp sau để tạo số ngẫu nhiên:

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

30
Tôi không phải là một trong những người bỏ phiếu, nhưng lưu ý rằng PNRG tiêu chuẩn phục vụ nhu cầu thực sự - tức là có thể lặp lại một chuỗi từ một hạt giống đã biết. Đôi khi chi phí tuyệt đối của một RNG mật mã thực sự là quá nhiều. Và đôi khi một RNG tiền điện tử là cần thiết. Ngựa cho các khóa học, để nói chuyện.
Marc Gravell

4
Theo tài liệu này, lớp này an toàn cho chủ đề, vì vậy đó là điều có lợi cho nó.
Rob Church

Xác suất hai chuỗi ngẫu nhiên là một và giống nhau bằng cách sử dụng đó là gì? Nếu chuỗi chỉ có 3 ký tự, tôi đoán điều này sẽ xảy ra với xác suất cao nhưng nếu độ dài 255 ký tự thì có thể có cùng một chuỗi ngẫu nhiên hay được đảm bảo rằng điều này không thể xảy ra từ thuật toán?
Lyubomir Velchev

16

1) Như Marc Gravell đã nói, hãy thử sử dụng MỘT bộ tạo ngẫu nhiên. Thật tuyệt khi thêm phần này vào hàm tạo: System.En Môi.TickCount.

2) Một mẹo. Giả sử bạn muốn tạo 100 đối tượng và giả sử mỗi đối tượng nên có trình tạo ngẫu nhiên của riêng mình (tiện dụng nếu bạn tính LOADS các số ngẫu nhiên trong một khoảng thời gian rất ngắn). Nếu bạn sẽ làm điều này trong một vòng lặp (tạo 100 đối tượng), bạn có thể làm điều này như thế (để đảm bảo tính ngẫu nhiên hoàn toàn):

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

Chúc mừng.


3
Tôi sẽ di chuyển System.En Môi.TickCount ra khỏi vòng lặp. Nếu nó tích tắc trong khi bạn lặp đi lặp lại thì bạn sẽ có hai mục được khởi tạo cho cùng một hạt giống. Một tùy chọn khác là kết hợp số đếm khác nhau (ví dụ: System.En Môi.TickCount << 8 + i)
Cá heo

Nếu tôi hiểu chính xác: ý bạn là, điều đó có thể xảy ra, rằng "System.En Môi.TickCount + i" có thể dẫn đến giá trị CÙNG?
Sontand

EDIT: Tất nhiên, không cần phải có TickCount trong vòng lặp. Lỗi của tôi :).
Sontand

2
Trình Random()xây dựng mặc định gọi bằng Random(Environment.TickCount)mọi cách
Alsty

5

Mỗi khi bạn thực hiện

Random random = new Random (15);

Không thành vấn đề nếu bạn thực hiện nó hàng triệu lần, bạn sẽ luôn sử dụng cùng một hạt giống.

Nếu bạn dùng

Random random = new Random ();

Bạn nhận được chuỗi số ngẫu nhiên khác nhau, nếu tin tặc đoán hạt giống và thuật toán của bạn có liên quan đến bảo mật hệ thống của bạn - thuật toán của bạn bị hỏng. Tôi bạn thực hiện nhiều. Trong hàm tạo này, hạt giống được chỉ định bởi đồng hồ hệ thống và nếu một số trường hợp được tạo trong một khoảng thời gian rất ngắn (mili giây) thì có thể chúng có cùng hạt giống.

Nếu bạn cần số ngẫu nhiên an toàn, bạn phải sử dụng lớp

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

Sử dụng:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. Điều đó không đúng trừ khi bạn tự xác định hạt giống.
LarsTech

Đã sửa. Cảm ơn Chính xác như bạn nói LarsTech, nếu cùng một hạt giống luôn được chỉ định, cùng một chuỗi các số ngẫu nhiên sẽ luôn được tạo. Trong câu trả lời của tôi, tôi đề cập đến hàm tạo với các tham số nếu bạn luôn sử dụng cùng một hạt giống. Lớp Random chỉ tạo các số ngẫu nhiên giả. Nếu ai đó phát hiện ra hạt giống nào bạn đã sử dụng trong thuật toán của mình, nó có thể ảnh hưởng đến tính bảo mật hoặc tính ngẫu nhiên của thuật toán của bạn. Với lớp RNGCryptoServiceProvider, bạn có thể có các số ngẫu nhiên một cách an toàn. Tôi đã sửa chữa, cảm ơn bạn rất nhiều vì đã sửa chữa.
Joma

0

chỉ cần khai báo biến lớp ngẫu nhiên như thế này:

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

nếu bạn muốn nhận số ngẫu nhiên khác nhau mỗi lần từ danh sách của mình thì hãy sử dụng

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

Mỗi lần bằng cách khai báo Random r = new Random()một lần.


Khi bạn gọi new Random()nó sử dụng đồng hồ hệ thống, nhưng nếu bạn gọi toàn bộ mã đó hai lần liên tiếp trước khi đồng hồ thay đổi, bạn sẽ nhận được cùng một số ngẫu nhiên. Đó là toàn bộ câu trả lời ở trên.
Savage

-1

Có rất nhiều giải pháp, ở đây là một: nếu bạn chỉ muốn xóa số chữ cái và phương thức nhận được ngẫu nhiên và độ dài kết quả.

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

Vấn đề cơ bản vẫn giống nhau - bạn đang chuyển qua một Randomthể hiện, nhưng bạn vẫn mong người gọi tạo một thể hiện được chia sẻ. Nếu người gọi tạo một phiên bản mới mỗi lần và mã được thực thi hai lần trước khi đồng hồ thay đổi, bạn sẽ nhận được cùng một số ngẫu nhiên. Vì vậy, câu trả lời này vẫn đưa ra các giả định có thể sai lầm.
Savage

Ngoài ra, toàn bộ vấn đề có một phương pháp để tạo các số ngẫu nhiên là đóng gói - rằng phương thức gọi đó không phải lo lắng về việc triển khai, nó chỉ quan tâm đến việc lấy lại một số ngẫu nhiên
Savage
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.