البرمجة غير المتزامنة في Go: من النظرية إلى التطبيق
تشتهر Go (Golang) بدعمها المدمج للبرمجة غير المتزامنة. على عكس العديد من اللغات الأخرى حيث تتطلب عدم التزامن مكتبات معقدة واستدعاءات رجعية، تقدم Go أدوات بديهية وقوية: الغوروتينات (goroutines) والقنوات (channels). في هذا الدليل، سنشرح جميع المفاهيم الأساسية عمليًا، حتى تتمكن من كتابة كود متوازٍ وفعال بلغة Go.
ما هي الغوروتينات (Goroutines)؟
الغوروتين هو خيط تنفيذ خفيف الوزن يُدار بواسطة بيئة تشغيل Go. على عكس خيوط النظام، تستهلك الغوروتينات موارد قليلة جدًا (بضعة كيلوبايتات فقط من المكدس)، مما يسمح بتشغيل آلاف بل ملايين الغوروتينات في وقت واحد.
تشغيل غوروتين بسيط للغاية —只需 إضافة الكلمة المفتاحية go قبل استدعاء الدالة:
package main
import ( "fmt" "time")
func sayHello() { fmt.Println("مرحبًا من الغوروتين!")}
func main() { go sayHello() // تشغيل الغوروتين time.Sleep(time.Millisecond * 100) // إعطاء وقت للغوروتين للتنفيذ fmt.Println("الدالة الرئيسية اكتملت")}لاحظ: إذا لم تُضف time.Sleep، فقد ينتهي البرنامج قبل أن يبدأ الغوروتين في التنفيذ. للمزامنة، من الأفضل استخدام آليات أخرى سنناقشها أدناه.
القنوات (Channels): نقل البيانات بين الغوروتينات
القنوات هي "أنابيب" محددة النوع يمكن للغوروتينات من خلالها تبادل البيانات. يتم إنشاء القناة باستخدام الدالة make والعامل chan. يتم إرسال البيانات إلى القناة باستخدام العامل <-، واستقبالها أيضًا باستخدام <- ولكن على يسار القناة.
لننظر إلى مثال بقناة مخزنة مؤقتًا وأخرى غير مخزنة:
package main
import "fmt"
func main() { // قناة غير مخزنة (حجم 0) ch := make(chan string) go func() { ch <- "رسالة من الغوروتين" // إرسال البيانات }() msg := <-ch // استقبال البيانات (يتم الحظر حتى تصل الرسالة) fmt.Println(msg) // قناة مخزنة (حجم 2) bufferedCh := make(chan int, 2) bufferedCh <- 10 bufferedCh <- 20 fmt.Println(<-bufferedCh) // 10 fmt.Println(<-bufferedCh) // 20}هام: القناة غير المخزنة تحظر المرسل حتى يستقبل المستقبل البيانات. القناة المخزنة تحظر المرسل فقط عندما يكون المخزن ممتلئًا.
Select: تعدد الإرسال للقنوات
العامل select يسمح للغوروتين بانتظار عدة قنوات في وقت واحد ومعالجة القناة الجاهزة أولاً. إنه مشابه لـ switch، لكن للقنوات. select أداة قوية لإنشاء مهلات زمنية، وإلغاء العمليات، والفحوصات غير المحظورة.
package main
import ( "fmt" "time")
func main() { ch1 := make(chan string) ch2 := make(chan string) // الغوروتين 1: يرسل البيانات بعد ثانية واحدة go func() { time.Sleep(1 * time.Second) ch1 <- "بيانات من القناة 1" }() // الغوروتين 2: يرسل البيانات بعد ثانيتين go func() { time.Sleep(2 * time.Second) ch2 <- "بيانات من القناة 2" }() select { case msg1 := <-ch1: fmt.Println("تم الاستلام:", msg1) case msg2 := <-ch2: fmt.Println("تم الاستلام:", msg2) case <-time.After(500 * time.Millisecond): fmt.Println("مهلة! لم تستجب أي قناة خلال 500 مللي ثانية") }}في هذا المثال، سيعمل select على المهلة، حيث لا توجد قناة جاهزة خلال 500 مللي ثانية. إذا أزلنا المهلة، سينتظر البرنامج أول قناة جاهزة