البرمجة غير المتزامنة في Go: دليل كامل للمبتدئين
\n\nالبرمجة غير المتزامنة هي نهج يمكن للبرنامج من خلاله تنفيذ مهام متعددة في وقت واحد دون حظر تنفيذ الخيط الرئيسي. في لغة Go (Golang)، تم تنفيذ هذه الآلية بشكل أنيق وبسيط بفضل البدائيات المدمجة: الجوروتينات (goroutines) و القنوات (channels). على عكس العديد من اللغات الأخرى، حيث تتطلب البرمجة غير المتزامنة استدعاءات معقدة أو وعود (promises)، تقدم Go بناء جملة موجز وأدوات قوية للعمل مع التزامن.
\n\nفي هذا الدليل، سنشرح أساسيات البرمجة غير المتزامنة في Go. ستتعرف على ماهية الجوروتينات، وكيفية إنشائها ومزامنتها باستخدام القنوات، وكذلك كيفية استخدام عامل select للعمل مع قنوات متعددة. المادة موجهة للمبتدئين، لذا ستكون جميع الأمثلة بسيطة وواضحة قدر الإمكان.
ما هي الجوروتينات وكيف تعمل
\n\nالجوروتين (goroutine) هو خيط تنفيذ خفيف الوزن يتم إدارته بواسطة بيئة تشغيل Go. على عكس خيوط نظام التشغيل العادية، تستهلك الجوروتينات موارد أقل بكثير: يبدأ مكدس الجوروتين ببضعة كيلوبايتات ويمكن أن يتوسع ديناميكيًا. يمكن تشغيل جوروتين ببساطة عن طريق إضافة الكلمة المفتاحية go قبل استدعاء الدالة:
package main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc printMessage(msg string) {\n for i := 0; i < 3; i++ {\n fmt.Println(msg)\n time.Sleep(100 * time.Millisecond)\n }\n}\n\nfunc main() {\n // تشغيل جوروتين\n go printMessage("مرحباً من الجوروتين!")\n \n // الدالة الرئيسية\n printMessage("مرحباً من main!")\n \n // انتظار انتهاء الجوروتين\n time.Sleep(1 * time.Second)\n}\n\nفي هذا المثال، يتم تشغيل الدالة printMessage مرتين: مرة كدالة عادية، والمرة الثانية كجوروتين. يتم التنفيذ بشكل متزامن: ستختلط الرسائل. لاحظ: بدون time.Sleep في نهاية main، سينتهي البرنامج دون انتظار اكتمال الجوروتين. للمزامنة الموثوقة، من الأفضل استخدام آليات خاصة.
القنوات: نقل آمن للبيانات بين الجوروتينات
\n\nالقنوات (channels) هي "أنابيب" محددة النوع يمكن للجوروتينات من خلالها تبادل البيانات. يتم إنشاء القناة باستخدام الدالة make وعامل chan. يتم إرسال البيانات إلى القناة عبر <-، ويتم استقبالها بنفس الصيغة.
لنلق نظرة على مثال أساسي:
\n\npackage main\n\nimport "fmt"\n\nfunc sendData(ch chan string) {\n ch <- "بيانات من الجوروتين"\n}\n\nfunc main() {\n // إنشاء قناة للنصوص\n messageChannel := make(chan string)\n \n // تشغيل جوروتين يرسل البيانات\n go sendData(messageChannel)\n \n // استقبال البيانات من القناة (يتم الحظر حتى الاستلام)\n message := <-messageChannel\n \n fmt.Println(message)\n}\n\nهنا، يرسل الجوروتين نصًا إلى القناة، وينتظر main استلامه. هام: عمليات القنوات تكون حاجزة (blocking) افتراضيًا. يتم حظر الإرسال إلى القناة حتى يتم استلام البيانات، والعكس صحيح. وهذا يضمن المزامنة الطبيعية.
يمكن أن تكون القنوات مخزنة مؤقتًا (buffered). يسمح المخزن المؤقت بإرسال البيانات دون حظر حتى يمتلئ المخزن:
\n\n// إنشاء قناة مخزنة بسعة عنصرين\nbufferedChannel := make(chan string, 2)\n\n// إرسال البيانات دون حظر\nbufferedChannel <- "الأول"\nbufferedChannel <- "الثاني"\n\n// الآن يمكن استقبال البيانات\nfmt.Println(<-bufferedChannel)\nfmt.Println(<-bufferedChannel)\n\nعامل select: العمل مع قنوات متعددة
\n\nعندما يكون لديك عدة قنوات، وتريد الاستجابة للقناة التي