ตัวสร้างตัวเลขสุ่มจะสร้างตัวเลขสุ่มเพียงหนึ่งตัวเท่านั้น


765

ฉันมีฟังก์ชั่นต่อไปนี้:

//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อาร์เรย์มีค่าเท่ากัน

ทำไมถึงเกิดขึ้น?


20
การใช้new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);ไม่ได้ผลตัวเลข "สุ่ม" ที่ดีไปกว่า.Next(0, 256)
bohdan_trotsenko

คุณอาจพบว่าแพ็คเกจ NuGet นี้มีประโยชน์ มันมีRand.Next(int, int)วิธีการคงที่ซึ่งให้การเข้าถึงแบบสุ่มค่าโดยไม่ต้องล็อคหรือวิ่งเข้าไปในปัญหาการใช้ซ้ำเมล็ด
ChaseMedallion

คำตอบ:


1042

ทุกครั้งที่คุณทำnew Random()มันจะเริ่มต้นได้โดยใช้นาฬิกา ซึ่งหมายความว่าในวงแคบคุณจะได้รับค่าเท่ากันหลายครั้ง คุณควรเก็บอินสแตนซ์แบบสุ่มเดียวและใช้Next ต่อไปบนอินสแตนซ์เดียวกัน

//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ที่นี่?

โดยทั่วไปNextกำลังจะเปลี่ยนสถานะภายในของRandomอินสแตนซ์ หากเราทำในเวลาเดียวกันจากหลายเธรดคุณสามารถโต้แย้งว่า "เราเพิ่งทำผลลัพธ์แบบสุ่มมากขึ้น" แต่สิ่งที่เราทำจริง ๆคือการทำลายการใช้งานภายในและเราอาจเริ่มได้รับตัวเลขเดียวกัน จากหัวข้อที่แตกต่างกันซึ่งอาจเป็นปัญหา - และอาจไม่ แม้ว่าการรับประกันว่าสิ่งที่เกิดขึ้นภายในจะเป็นปัญหาที่ใหญ่กว่าก็ตาม เนื่องจากRandomไม่ได้รับประกันความปลอดภัยของเธรด ดังนั้นจึงมีวิธีการที่ใช้ได้สองวิธี:

  • ซิงโครไนซ์เพื่อให้เราไม่สามารถเข้าถึงได้ในเวลาเดียวกันจากกระทู้ที่แตกต่างกัน
  • ใช้Randomอินสแตนซ์ต่าง ๆต่อเธรด

ทั้งสามารถปรับ; แต่การปิดการใช้งานอินสแตนซ์เดียวจากผู้โทรหลายคนพร้อมกันก็แค่ถามหาปัญหา

lockประสบความสำเร็จในครั้งแรก (และง่าย) ของวิธีการเหล่านี้ อย่างไรก็ตามวิธีการอื่นอาจเป็น:

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

นี่คือต่อเธรดดังนั้นคุณไม่จำเป็นต้องซิงโครไนซ์


19
ตามกฎทั่วไปวิธีการคงที่ทั้งหมดควรทำให้ปลอดภัยต่อเธรดเนื่องจากเป็นการยากที่จะรับประกันได้ว่าหลายเธรดจะไม่เรียกใช้ในเวลาเดียวกัน มันเป็นไม่ได้มักจะจำเป็นที่จะทำให้อินสแตนซ์ (คือไม่คงที่) วิธีด้ายปลอดภัย
Marc Gravell

5
@Florin - ไม่มีความแตกต่างกันระหว่าง "stack based" ฟิลด์สแตติกเป็นเพียง "สถานะภายนอก" มากและจะถูกแชร์ระหว่างผู้โทรอย่างแน่นอน ด้วยอินสแตนซ์มีโอกาสที่เธรดที่ต่างกันจะมีอินสแตนซ์ต่างกัน (รูปแบบทั่วไป) ด้วยสถิตศาสตร์รับประกันได้ว่าพวกเขาแบ่งปันทั้งหมด (ไม่รวม [ThreadStatic])
Marc Gravell

2
@gdoron คุณได้รับข้อผิดพลาดหรือไม่? "การล็อค" ควรป้องกันไม่ให้กระทู้จากสะดุดแต่ละอื่น ๆ ที่นี่ ...
Marc Gravell

6
@Dan ถ้าวัตถุนั้นไม่เคยเปิดเผยต่อสาธารณะ: คุณทำได้ ความเสี่ยง (ในทางทฤษฎี) คือเธรดอื่น ๆ กำลังล็อคอยู่ในลักษณะที่คุณไม่คาดคิด
Marc Gravell

3
@smiron มีโอกาสมากที่คุณเพียงแค่ใช้การสุ่มนอกล็อคเช่นกัน การล็อกไม่ได้ป้องกันการเข้าถึงสิ่งที่คุณกำลังล็อกอยู่ทั้งหมด - เพียงตรวจสอบให้แน่ใจว่าคำสั่งการล็อกสองรายการในอินสแตนซ์เดียวกันจะไม่ทำงานพร้อมกัน ดังนั้นlock (syncObject)จะช่วยถ้าทุก สายยังอยู่random.Next() lock (syncObject)หากสถานการณ์ที่คุณอธิบายเกิดขึ้นแม้จะมีlockการใช้งานที่ถูกต้องมันก็มีแนวโน้มที่จะเกิดขึ้นอย่างมากในสถานการณ์แบบเธรดเดียว (เช่นRandomมีการแตกย่อย)
Luaan

118

เพื่อความสะดวกในการนำกลับมาใช้ใหม่ตลอดทั้งแอปพลิเคชันของคุณคลาสแบบสแตติกอาจช่วยได้

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

62

โซลูชันของ 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);
    }
}

วัดการใช้งานทั้งสองและคุณควรเห็นความแตกต่างที่สำคัญ


12
ล็อคมีราคาถูกมากเมื่อพวกเขาไม่ได้ถูกโต้แย้ง ... และแม้ว่าฉันจะโต้แย้งฉันคาดหวังว่า "ตอนนี้ทำอะไรกับหมายเลข" รหัสเพื่อแคระค่าใช้จ่ายของล็อคในสถานการณ์ที่น่าสนใจที่สุด
Marc Gravell

4
เห็นด้วยนี่เป็นการแก้ปัญหาการล็อค แต่นี่ไม่ใช่วิธีการแก้ปัญหาที่ซับซ้อนอย่างมากสำหรับปัญหาเล็ก ๆ น้อย ๆ นั่นคือคุณต้องเขียนโค้ด '' สอง '' บรรทัดเพื่อสร้างตัวเลขสุ่มแทนที่จะเป็นหนึ่ง สิ่งนี้คุ้มค่าหรือไม่ที่จะบันทึกการอ่านโค้ดหนึ่งบรรทัด?
EMP

4
+1 การใช้Randomอินสแตนซ์ระดับโลกเพิ่มเติมเพื่อรับเมล็ดเป็นความคิดที่ดี โปรดทราบด้วยว่าโค้ดสามารถลดความซับซ้อนลงได้อีกโดยใช้ThreadLocal<T>คลาสที่แนะนำใน. NET 4 (ตามที่ Phil เขียนไว้ด้านล่าง )
Groo

40

คำตอบของฉันจากที่นี่ :

เพียงแค่ทำซ้ำคำตอบที่ถูกต้อง :

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

ไม่เพียง แต่จะช้ากว่า (ภายในวง) การสุ่มของมันคือ ... ไม่ดีตามฉันเลย ..


10
ฉันไม่เห็นด้วยกับกรณี Guid ชั้นสุ่มใช้การกระจายแบบสม่ำเสมอ ซึ่งไม่ใช่ใน Guid เป้าหมาย Guid คือการไม่ซ้ำกันไม่กระจายอย่างสม่ำเสมอ (และการดำเนินการเป็นส่วนใหญ่ขึ้นอยู่กับคุณสมบัติของฮาร์ดแวร์ / เครื่องซึ่งตรงข้ามกับ ... randomness)
Askolein

4
หากคุณไม่สามารถพิสูจน์ความสม่ำเสมอของรุ่น Guid ได้มันผิดที่จะใช้มันเป็นแบบสุ่ม (และแฮชจะเป็นอีกขั้นหนึ่งที่ห่างจากความสม่ำเสมอ) การชนกันไม่ใช่ปัญหา: ความสม่ำเสมอของการชนคือ เกี่ยวกับรุ่น Guid ไม่ได้อยู่ในฮาร์ดแวร์อีกต่อไปฉันจะ RTFM ไม่ดีของฉัน (อ้างอิงใด ๆ ?)
Askolein

5
มีความเข้าใจสองอย่างของ "การสุ่ม": 1. การขาดรูปแบบหรือ 2. การขาดรูปแบบตามวิวัฒนาการที่อธิบายโดยการแจกแจงความน่าจะเป็น (2 รวมอยู่ใน 1) ตัวอย่าง Guid ของคุณถูกต้องในกรณีที่ 1 ไม่ใช่ในกรณีที่ 2 ตรงกันข้าม: Randomคลาสตรงกับกรณีที่ 2 (เช่นกรณีที่ 1 ด้วย) คุณสามารถแทนที่การใช้งานRandomโดยของคุณGuid+Hashหากคุณไม่ได้อยู่ในกรณีที่ 2 กรณีที่ 1 อาจเพียงพอที่จะตอบคำถามแล้วGuid+Hashงานของคุณก็ดี แต่ก็ไม่ได้กล่าวอย่างชัดเจน (PS: ชุดนี้ )
Askolein

2
@Askolein สำหรับข้อมูลการทดสอบบางอย่างฉันเรียกใช้ชุดข้อมูลทั้งสองRandomและGuid.NewGuid().GetHashCode()ผ่าน Ent ( fourmilab.ch/random ) และทั้งสองแบบสุ่มในทำนองเดียวกัน new Random(Guid.NewGuid().GetHashCode())ใช้งานได้ดีเช่นเดียวกับการใช้ "master" ที่ซิงโครไนซ์Randomเพื่อสร้างเมล็ดสำหรับ "เด็ก" Randomแน่นอนว่ามันขึ้นอยู่กับว่าระบบของคุณสร้าง Guids - สำหรับระบบของฉันพวกมันค่อนข้างสุ่มและอื่น ๆ แม้จะสุ่ม crypto ทุกวันนี้ Windows หรือ MS SQL ดูใช้ได้ดี แม้ว่าโมโนและ / หรือมือถืออาจแตกต่างกัน
Luaan

2
@EdB ตามที่ฉันได้กล่าวไว้ในความคิดเห็นก่อนหน้านี้ในขณะที่ Guid (จำนวนมาก) มีความหมายที่ไม่ซ้ำกันGetHashCodeGuid ใน. NET ได้มาจากการแสดงสตริง ผลลัพธ์ค่อนข้างสุ่มสำหรับความชอบของฉัน
nawfal

27

ฉันอยากจะใช้คลาสต่อไปนี้เพื่อสร้างตัวเลขสุ่ม:

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

30
ฉันไม่ได้เป็นหนึ่งในผู้ลงคะแนน แต่โปรดทราบว่า PNRG มาตรฐานตอบสนองความต้องการที่แท้จริง - นั่นคือสามารถทำซ้ำลำดับจากเมล็ดที่รู้จักได้ บางครั้งค่าใช้จ่ายที่แท้จริงของการเข้ารหัสลับ RNG จริงมากเกินไป และบางครั้งจำเป็นต้องใช้ Crypto RNG ม้าสำหรับหลักสูตรดังนั้นที่จะพูด
Marc Gravell

4
ตามเอกสารประกอบคลาสนี้ปลอดภัยสำหรับเธรด
Rob Church

ความน่าจะเป็นที่เป็นไปได้สองสตริงสุ่มที่จะเป็นหนึ่งและเหมือนกันเมื่อใช้นั้นคืออะไร? หากสตริงมีเพียง 3 ตัวอักษรฉันเดาว่าจะเกิดขึ้นกับความน่าจะเป็นสูง แต่ถ้าเป็น 255 ความยาวตัวอักษรเป็นไปได้ที่จะมีสตริงสุ่มแบบเดียวกันหรือรับประกันได้ว่าจะไม่เกิดขึ้นจากอัลกอริทึม?
Lyubomir Velchev

16

1) ตามที่ Marc Gravell กล่าวว่าพยายามใช้เครื่องกำเนิดไฟฟ้าแบบสุ่มหนึ่งตัว มันยอดเยี่ยมเสมอที่จะเพิ่มสิ่งนี้ลงในตัวสร้าง: 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);

ไชโย


3
ฉันจะย้าย System.Environment.TickCount ออกจากลูป ถ้ามันเห็บในขณะที่คุณกำลังวนซ้ำแล้วคุณจะมีสองรายการเริ่มต้นไปยังเมล็ดเดียวกัน อีกตัวเลือกหนึ่งที่จะรวม tickcount ที่ฉันแตกต่างกัน (เช่น System.Environment.TickCount << 8 + i)
ดอลฟิ

ถ้าฉันเข้าใจถูกต้อง: คุณหมายถึงมันอาจเกิดขึ้นได้ว่า "System.Environment.TickCount + i" อาจส่งผลให้ค่าเดียวกัน
sabiland

แก้ไข: แน่นอนไม่จำเป็นต้องมี TickCount ภายในลูป ความผิดฉันเอง :).
sabiland

2
ตัวRandom()สร้างเริ่มต้นเรียกRandom(Environment.TickCount)ต่อไป
Alsty

5

ทุกครั้งที่คุณดำเนินการ

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

It does not matter if you execute it millions of times, you will always use the same seed. นั่นไม่เป็นความจริงเว้นแต่คุณจะระบุเมล็ดพันธุ์เอง
LarsTech

ซ่อมแซม. ขอบคุณเหมือนที่คุณบอกว่า LarsTech ถ้ามีการระบุเมล็ดเดียวกันเสมอจะมีการสร้างหมายเลขสุ่มลำดับเดียวกันเสมอ ในคำตอบของฉันฉันหมายถึงคอนสตรัคด้วยพารามิเตอร์ถ้าคุณใช้เมล็ดเดียวกันเสมอ คลาส Random สร้างตัวเลขสุ่มหลอกเท่านั้น หากมีคนรู้ว่าคุณใช้เมล็ดพันธุ์แบบใดในอัลกอริทึมของคุณมันสามารถลดความปลอดภัยหรือความสุ่มของอัลกอริทึมของคุณ ด้วยคลาส RNGCryptoServiceProvider คุณสามารถมีตัวเลขสุ่มได้อย่างปลอดภัย ฉันแก้ไขแล้วขอบคุณมากสำหรับการแก้ไข
Joma

0

เพียงประกาศตัวแปรคลาส Random เช่นนี้

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

หากคุณต้องการรับหมายเลขสุ่มที่แตกต่างกันในแต่ละครั้งจากรายการของคุณให้ใช้

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

แต่ละครั้งโดยประกาศRandom r = new Random()เพียงครั้งเดียว


เมื่อคุณโทรnew Random()มันจะใช้นาฬิการะบบ แต่ถ้าคุณเรียกรหัสทั้งหมดนั้นสองครั้งติดต่อกันก่อนที่นาฬิกาจะเปลี่ยนคุณจะได้รับหมายเลขสุ่มที่เหมือนกัน นั่นคือประเด็นทั้งหมดของคำตอบข้างต้น
โหด

-1

มีวิธีแก้ปัญหามากมายที่นี่ข้อหนึ่ง: ถ้าคุณต้องการเพียงแค่จำนวนลบตัวอักษรและวิธีการได้รับการสุ่มและความยาวผลลัพธ์

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

ปัญหาพื้นฐานยังคงเหมือนเดิม - คุณกำลังผ่านRandomอินสแตนซ์ แต่คุณยังคาดหวังว่าผู้โทรจะสร้างอินสแตนซ์ที่แชร์ หากผู้เรียกสร้างอินสแตนซ์ใหม่ทุกครั้งและมีการเรียกใช้รหัสสองครั้งก่อนที่นาฬิกาจะเปลี่ยนคุณจะได้รับหมายเลขสุ่มเหมือนกัน ดังนั้นคำตอบนี้ยังคงตั้งสมมติฐานซึ่งอาจผิดพลาดได้
โหด

นอกจากนี้จุดรวมของวิธีการสร้างตัวเลขแบบสุ่มคือการห่อหุ้ม - วิธีการโทรไม่ต้องกังวลเกี่ยวกับการใช้งานมันเป็นเพียงความสนใจในการรับหมายเลขแบบสุ่มกลับมา
Savage
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.