العشوائية أمر خطير جداً لتركها للصدفة
\n\nكل من كتب كوداً في Go ولو مرة واحدة، واجه توليد الأرقام العشوائية. مولد الأرقام العشوائية (RNG) شيء خادع. يبدو الأمر بسيطاً: استدع rand.Intn() وستحصل على رقم. ولكن إذا لم تهتم بـ التهيئة الصحيحة (seed)، فإن "العشوائية" الخاصة بك ستتحول إلى تسلسل متوقع، مما سيعطل الاختبارات، ويخذلك في التشفير، أو ببساطة سيفاجئك بنتائج متطابقة في كل تشغيل.
على Stack Overflow، جمع سؤال "كيفية تهيئة مولد الأرقام العشوائية بشكل صحيح" آلاف المشاهدات. يخلط الناس بين time.Now().UnixNano()، ويستخدمون crypto/rand حيث لا داعي لذلك، ويعانون من ضعف الأداء. اليوم سنحلل هذا الموضوع باستخدام Go - بدون حشو، مع مقاييس أداء حقيقية وحلول جاهزة.
لماذا التهيئة (seed) ليست مجرد إجراء شكلي؟
\n\nالتهيئة (Seed) هي الحالة الأولية للمولد. نفس التهيئة تعطي نفس تسلسل الأرقام. هذا مفيد لتصحيح الأخطاء، لكنه قاتل في بيئة الإنتاج إذا كنت تتوقع عدم القدرة على التوقع.
\n\nفي Go، تستخدم الحزمة القياسية math/rand خوارزمية حتمية. إذا نسيت استدعاء rand.Seed()، ف... قبل Go 1.20، كان المولد الافتراضي يستخدم تهيئة ثابتة (عادة 1). بدءاً من Go 1.20، يتم تهيئة math/rand تلقائياً بقيمة عشوائية عند بدء البرنامج. ومع ذلك، الاعتماد على هذا في الأماكن الحرجة هو ممارسة سيئة.
المشكلة الرئيسية للمبتدئين هي محاولة تهيئة المولد لكل رقم عشوائي:
\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 ينشئ كائناً جديداً، مما يثقل كاهل جامع القمامة.
أفضل 3 طرق للتهيئة في Go
\n\n1. الطريقة التقليدية: تهيئة عامة مرة واحدة
\n\nالخيار الأبسط والأصح لمعظم التطبيقات هو تعيين التهيئة مرة واحدة عند بدء البرنامج.
\n\npackage 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% من التطبيقات.
2. مولد محلي للعزل
\n\nعندما يكون لديك عدة goroutines أو لا تريد التأثير على الحالة العامة، قم بإنشاء مثيل خاص بك من rand.Rand.
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).
3. تهيئة مشفرة للمهام الحرجة
\n\nإذا كنت بحاجة إلى عشوائية حقيقية (رموز، كلمات مرور، تشفير) - انسَ math/rand. استخدم crypto/rand:
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