المستوى: مبتدئ — هذا المقال يفترض إنك كتبت كود من قبل بأي لغة (JavaScript أو Python أو حتى C)، ولسه ما لمستش Go فعلياً. متوسط القراءة: 8 دقائق.
لو السكربت بتاعك بياخد 50 ثانية علشان يرسل 100 إيميل واحد ورا التاني، Goroutines في Go بتنزّل الزمن لـ 0.6 ثانية بـ 6 سطور كود فعلية، بدون threads ولا queue ولا أي تعقيد إضافي. الموضوع مش "Go أسرع من Python" — الموضوع إن Go صمم concurrency جواه من اليوم الأول، وده اللي بيخلّي السطر go funcName() يقوم بشغل أيام عند مهندسين تانيين بـ ThreadPoolExecutor و asyncio.
Goroutines: المهام المتزامنة بسطر واحد فعلاً
المشكلة باختصار
أي تطبيق حقيقي بيعمل I/O كتير — بيقرا من DB، بيبعت HTTP requests، بيكتب logs، بيرسل إيميلات. لو نفّذت العمليات دي تسلسلياً، السيرفر بيفضل واقف 90% من الوقت بيستنّى الشبكة. الحلول التقليدية (threads، processes، asyncio) بتشتغل، لكن كل واحدة بثمن: threads ثقيلة على الذاكرة، processes ثقيلة على الـ CPU، asyncio محتاج تفصّل كل سطر كود ليكون async/await. Goroutines بتديك نفس الفايدة بنصف الكلفة الذهنية.
ابدأ بمثال — صرّاف البنك
تخيل بنك فيه شبّاك واحد بس. 100 عميل واقفين في طابور، كل عميل عمليته بتاخد نص ثانية. آخر عميل في الطابور هيستنى 50 ثانية. ده الكود التسلسلي.
دلوقتي افتح 100 شبّاك في نفس الفرع، كل صرّاف بيخدم عميل واحد. آخر عميل بيخلّص في نص ثانية. ده اللي Goroutines بتعمله بالظبط، لكن الشبابيك مش بشر — هي مهام صغيرة جداً (2 كيلوبايت لكل واحدة في البداية) بتشتغل كلها على نفس الـ CPU بترتيب ذكي يديره runtime مدمج في اللغة.
الفرق المهم عن threads: لو فتحت 100 thread في Java، كل واحد بياكل 1 ميجا ذاكرة كـ stack افتراضي، يعني 100 ميجا قبل ما تكتب أي logic. 100 goroutine بتاكل 200 كيلوبايت إجمالي. الفرق 500 ضعف.
التعريف العلمي بدون مجاملة
Goroutine هي "lightweight thread" يديرها Go runtime مش الـ OS مباشرة. الفرق الجوهري إن thread في Linux بياخد افتراضياً 8 ميجا من الذاكرة (مع overcommit بيتحجز فعلياً أقل، لكن الـ kernel بيحسبها)، الـ Goroutine بتبتدي بـ 2 كيلوبايت وبتنمو لو محتاجة. ده معناه إن مليون goroutine ممكن تتشغّل على سيرفر بـ 4 جيجا RAM، بينما مليون OS thread هتموّت أي ماكينة.
الـ Go runtime بيستخدم نموذج اسمه M:N scheduler. M = goroutines (يمكن مليون)، N = OS threads (افتراضياً بعدد cores الـ CPU، عادي 4–16). الـ scheduler بيوزّع الـ M على الـ N بشكل cooperative، ولما goroutine تستنّى I/O (network، disk، channel)، الـ scheduler بيدّي الـ thread لـ goroutine تانية فوراً بدل ما الـ thread يفضل واقف. ده اسمه "work stealing scheduler" واتطوّر من ورقة Dmitry Vyukov في 2012.
الكود الفعلي — قبل وبعد
هنبص على نفس المهمة بالظبط: إرسال 100 إيشعار. النسخة الأولى تسلسلية، النسخة الثانية بـ Goroutines.