كيفية تهيئة مولد الأرقام العشوائية في Go: أسرار الأداء

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

العشوائية أمر خطير جداً لتركها للصدفة

\n\n

كل من كتب كوداً في Go ولو مرة واحدة، واجه توليد الأرقام العشوائية. مولد الأرقام العشوائية (RNG) شيء خادع. يبدو الأمر بسيطاً: استدع rand.Intn() وستحصل على رقم. ولكن إذا لم تهتم بـ التهيئة الصحيحة (seed)، فإن "العشوائية" الخاصة بك ستتحول إلى تسلسل متوقع، مما سيعطل الاختبارات، ويخذلك في التشفير، أو ببساطة سيفاجئك بنتائج متطابقة في كل تشغيل.

\n\n

على Stack Overflow، جمع سؤال "كيفية تهيئة مولد الأرقام العشوائية بشكل صحيح" آلاف المشاهدات. يخلط الناس بين time.Now().UnixNano()، ويستخدمون crypto/rand حيث لا داعي لذلك، ويعانون من ضعف الأداء. اليوم سنحلل هذا الموضوع باستخدام Go - بدون حشو، مع مقاييس أداء حقيقية وحلول جاهزة.

\n\n

لماذا التهيئة (seed) ليست مجرد إجراء شكلي؟

\n\n

التهيئة (Seed) هي الحالة الأولية للمولد. نفس التهيئة تعطي نفس تسلسل الأرقام. هذا مفيد لتصحيح الأخطاء، لكنه قاتل في بيئة الإنتاج إذا كنت تتوقع عدم القدرة على التوقع.

\n\n

في Go، تستخدم الحزمة القياسية math/rand خوارزمية حتمية. إذا نسيت استدعاء rand.Seed()، ف... قبل Go 1.20، كان المولد الافتراضي يستخدم تهيئة ثابتة (عادة 1). بدءاً من Go 1.20، يتم تهيئة math/rand تلقائياً بقيمة عشوائية عند بدء البرنامج. ومع ذلك، الاعتماد على هذا في الأماكن الحرجة هو ممارسة سيئة.

\n\n

المشكلة الرئيسية للمبتدئين هي محاولة تهيئة المولد لكل رقم عشوائي:

\n\n
// ❌ سيء: إعادة إنشاء المولد في كل مرة\nfunc getRandom() int {\n    r := rand.New(rand.NewSource(time.Now().UnixNano()))\n    return r.Intn(100)\n}
\n\n

لماذا هذا سيء؟ time.Now().UnixNano() يعطي نانو ثانية، لكن في الواقع الدقة تعتمد على نظام التشغيل. في Windows، على سبيل المثال، قد تكون ~15 مللي ثانية. إذا استدعيت الدالة مرتين متتاليتين - ستحصل على أرقام متطابقة. بالإضافة إلى ذلك، كل استدعاء لـ rand.NewSource ينشئ كائناً جديداً، مما يثقل كاهل جامع القمامة.

\n\n

أفضل 3 طرق للتهيئة في Go

\n\n

1. الطريقة التقليدية: تهيئة عامة مرة واحدة

\n\n

الخيار الأبسط والأصح لمعظم التطبيقات هو تعيين التهيئة مرة واحدة عند بدء البرنامج.

\n\n
package main\n\nimport (\n    "fmt"\n    "math/rand"\n    "time"\n)\n\nfunc main() {\n    // ✅ جيد: التهيئة مرة واحدة\n    rand.Seed(time.Now().UnixNano())\n\n    for i := 0; i < 5; i++ {\n        fmt.Println(rand.Intn(100))\n    }\n}
\n\n

هام: time.Now().UnixNano() يعطي قيمة فريدة، لكن إذا تم تشغيل البرنامج عدة مرات في نفس النانو ثانية (غير محتمل، لكن نظرياً ممكن)، ستكون التهيئة متطابقة. هذا كافٍ لـ 99% من التطبيقات.

\n\n

2. مولد محلي للعزل

\n\n

عندما يكون لديك عدة goroutines أو لا تريد التأثير على الحالة العامة، قم بإنشاء مثيل خاص بك من rand.Rand.

\n\n
func NewSeededGenerator() *rand.Rand {\n    seed := time.Now().UnixNano()\n    return rand.New(rand.NewSource(seed))\n}\n\n// الاستخدام:\nrng := NewSeededGenerator()\nfor i := 0; i < 3; i++ {\n    fmt.Println(rng.Intn(100))\n}
\n\n

هذا النهج يعطي تحكماً كاملاً: يمكن أن يكون للـ goroutines المختلفة مولداتها الخاصة، ولا تمنع بعضها البعض (يستخدم rand العام كائن mutex).

\n\n

3. تهيئة مشفرة للمهام الحرجة

\n\n

إذا كنت بحاجة إلى عشوائية حقيقية (رموز، كلمات مرور، تشفير) - انسَ math/rand. استخدم crypto/rand:

\n\n
import (\n    "crypto/rand"\n    "encoding/binary"\n    "math/rand"\n)\n\nfunc CryptoSeed() int64 {\n    var buf [8]byte\n    _, err := rand.Read(buf[:])\n    if err != nil {\n                

المدونات

توصيات الكتب