أساسيات الـ Goroutines
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 59 من 107.
الـ goroutine هي خيط تنفيذ خفيف الوزن يتم إدارته بواسطة بيئة تشغيل Go (Go runtime). وعلى عكس الخيوط (threads) التقليدية التي تستهلك ذاكرة كبيرة، تبدأ الـ goroutines ببضعة كيلوبايتات فقط من مساحة المكدس (stack space)، مما يسمح لك بتشغيل الآلاف منها بشكل متزامن.
لبدء goroutine، ما عليك سوى وضع الكلمة المفتاحية go قبل استدعاء الدالة:
func sayHello(name string) {
fmt.Println("Hello,", name)
}
func main() {
go sayHello("Alice") // يتم تشغيله بشكل متزامن
go sayHello("Bob") // يتم تشغيله بشكل متزامن
time.Sleep(100 * time.Millisecond)
fmt.Println("Done")
}عندما يتم تنفيذ go sayHello("Alice")، فإنه لا ينتظر sayHello حتى ينتهي. بدلاً من ذلك، يستمر فوراً إلى السطر التالي بينما يعمل sayHello في الخلفية. هذا هو السبب في حاجتنا إلى time.Sleep—فبدونه، سيخرج main قبل اكتمال الـ goroutines.
يمكنك أيضاً تشغيل goroutines باستخدام دوال مجهولة (anonymous functions):
go func(msg string) {
fmt.Println(msg)
}("Hello from goroutine")تعمل الدالة main نفسها في goroutine. عندما تنتهي (returns)، يتم إنهاء جميع الـ goroutines الأخرى فوراً—ولا تتاح لها الفرصة للانتهاء. يعد استخدام time.Sleep حلاً مؤقتاً؛ في الدروس القادمة، ستتعلم تقنيات المزامنة الصحيحة باستخدام القنوات (channels) ومجموعات الانتظار (wait groups).
التحدي
سهللنقم ببناء مشغل مهام (task runner) ينفذ مهاماً متعددة بشكل متزامن باستخدام الـ goroutines. ستقوم بإنشاء نظام بسيط حيث تعمل المهام في الخلفية بينما يستمر البرنامج الرئيسي، مما يوضح كيف تمكن الـ goroutines من التنفيذ المتزامن.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
tasks.go: حدد منطق تنفيذ المهام الخاص بك.أنشئ هيكلاً
Taskيحتوي على حقولName(string) وDuration(int، يمثل المللي ثانية).قم بتنفيذ تابع
Run()علىTaskيحاكي العمل عن طريق النوم (sleeping) لمدة المهمة، ثم يطبع:Task [Name] completedأنشئ دالة
RunAllTasks(tasks []Task)تقوم بتشغيل تابعRun()لكل مهمة كـ goroutine. بعد تشغيل جميع الـ goroutines، يجب أن تنام الدالة لمدة 500 مللي ثانية للسماح لها بالاكتمال، ثم تطبع:All tasks launchedmain.go: قراءة معلومات المهام وتنسيق التنفيذ المتزامن.اقرأ عدد المهام، ثم اقرأ اسم كل مهمة ومدتها. أنشئ المهام ومررها إلى
RunAllTasks. بعد عودتها، اطبع:Main function done
سيتم توفير المدخلات التالية:
- السطر 1: عدد المهام (عدد صحيح)
- الأسطر التالية: لكل مهمة، سطران - اسم المهمة، ثم مدتها بالمللي ثانية
على سبيل المثال، إذا كان المعطى:
3
Download
100
Process
50
Upload
75يجب أن يظهر مخرجك اكتمال المهام (قد يختلف الترتيب بسبب التزامن)، متبوعاً برسائل الاكتمال:
Task Process completed
Task Upload completed
Task Download completed
All tasks launched
Main function doneملاحظة: نظراً لأن الـ goroutines تعمل بشكل متزامن، فإن المهام ذات المدد الأقصر تنتهي عادةً أولاً. يعتمد ترتيب اكتمال المهام على مددها - المهام الأقصر تكتمل قبل المهام الأطول.
استخدم time.Sleep مع time.Duration و time.Millisecond للتأخيرات. تذكر أنه بدون النوم في RunAllTasks، ستعود الدالة قبل أن تنهي الـ goroutines عملها.
ورقة مرجعية
الـ goroutine هي خيط تنفيذ (thread) خفيف الوزن تتم إدارته بواسطة بيئة تشغيل Go (Go runtime). تبدأ الـ goroutines بمساحة مكدس (stack space) تبلغ بضعة كيلوبايتات فقط، مما يسمح لك بتشغيل الآلاف منها بشكل متزامن.
لبدء goroutine، ضع الكلمة المفتاحية go قبل استدعاء الدالة:
func sayHello(name string) {
fmt.Println("Hello,", name)
}
func main() {
go sayHello("Alice") // يتم تشغيله بشكل متزامن
go sayHello("Bob") // يتم تشغيله بشكل متزامن
time.Sleep(100 * time.Millisecond)
fmt.Println("Done")
}عندما يتم تنفيذ go sayHello("Alice")، فإنه لا ينتظر انتهاء sayHello. بدلاً من ذلك، ينتقل فوراً إلى السطر التالي بينما يتم تشغيل sayHello في الخلفية.
يمكنك إطلاق goroutines باستخدام دوال مجهولة (anonymous functions):
go func(msg string) {
fmt.Println(msg)
}("Hello from goroutine")الدالة main نفسها تعمل في goroutine. عندما تنتهي، يتم إنهاء جميع الـ goroutines الأخرى فوراً. استخدم time.Sleep للسماح للـ goroutines بالاكتمال قبل خروج الدالة main.
جرّب بنفسك
package main
import (
"fmt"
)
func main() {
// قراءة عدد المهام
var numTasks int
fmt.Scanln(&numTasks)
// TODO: إنشاء شريحة (slice) للاحتفاظ بالمهام
// TODO: قراءة اسم كل مهمة ومدتها
// لكل مهمة:
// - قراءة اسم المهمة
// - قراءة المدة (كعدد صحيح)
// - إنشاء مهمة (Task) وإضافتها إلى الشريحة الخاصة بك
// TODO: استدعاء RunAllTasks مع شريحة المهام الخاصة بك
// TODO: طباعة "Main function done"
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool