التزامن وتعدد الخيوط في Go: المستوى المتقدم
\n\nالتزامن (concurrency) — إحدى الميزات الرئيسية للغة Go، المضمنة في بنيتها على مستوى وقت التشغيل وبناء الجملة. على عكس العديد من اللغات الأخرى حيث يتم تنفيذ تعدد الخيوط من خلال مكتبات معقدة ويتطلب إدارة يدوية للخيوط، تقدم Go غوروتينات (goroutines) خفيفة وقنوات (channels). على المستوى المتقدم، من المهم ليس فقط تشغيل عدة مهام، ولكن مزامنتها بشكل صحيح، وتجنب سباقات البيانات، واستخدام موارد النظام بكفاءة. في هذه المقالة، سنستعرض التقنيات المتقدمة للعمل مع التزامن في Go.
\n\n1. الغوروتينات: إدارة دورة الحياة والأولويات
\n\nالغوروتينات هي خيوط خفيفة الوزن تُدار بواسطة وقت تشغيل Go. يتم تشغيلها باستخدام الكلمة المفتاحية go. ومع ذلك، على المستوى المتقدم، تحتاج إلى فهم كيفية التحكم في تنفيذها وإنهائها.
تشغيل غوروتين أساسي:
\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc worker(id int) {\n fmt.Printf("Worker %d starting\\n", id)\n time.Sleep(time.Second)\n fmt.Printf("Worker %d done\\n", id)\n}\n\nfunc main() {\n for i := 1; i <= 5; i++ {\n go worker(i)\n }\n time.Sleep(2 * time.Second) // حل مؤقت: ننتظر الانتهاء\n}\n\nالمشكلة: في هذا المثال، نستخدم time.Sleep للانتظار، وهو أمر غير موثوق. على المستوى المتقدم، تُستخدم القنوات و sync.WaitGroup.
2. المزامنة باستخدام sync.WaitGroup
\n\nsync.WaitGroup يسمح بانتظار اكتمال عدد محدد من الغوروتينات. هذه أداة أساسية لكنها قوية للمزامنة.
package main\n\nimport (\n "fmt"\n "sync"\n "time"\n)\n\nfunc worker(id int, wg *sync.WaitGroup) {\n defer wg.Done() // تقليل العداد عند الانتهاء\n fmt.Printf("Worker %d starting\\n", id)\n time.Sleep(time.Second)\n fmt.Printf("Worker %d done\\n", id)\n}\n\nfunc main() {\n var wg sync.WaitGroup\n for i := 1; i <= 5; i++ {\n wg.Add(1) // زيادة العداد قبل التشغيل\n go worker(i, &wg)\n }\n wg.Wait() // الحظر حتى يصبح العداد صفرًا\n fmt.Println("All workers completed")\n}\n\nهام: يجب استدعاء wg.Add() قبل تشغيل الغوروتين، وإلا فقد يحدث سباق بيانات. استخدم defer wg.Done() داخل الغوروتين لضمان تقليل العداد حتى في حالة حدوث خطأ (panic).
3. القنوات: التخزين المؤقت، الاتجاه، و select
\n\nالقنوات هي الآلية الأساسية لتبادل البيانات بين الغوروتينات. على المستوى المتقدم، تُستخدم القنوات المخزنة مؤقتًا، والقنوات أحادية الاتجاه، والعامل select.
3.1. القنوات المخزنة مؤقتًا (Buffered Channels)
\nالقنوات المخزنة مؤقتًا تسمح بإرسال البيانات دون استلام فوري. هذا يقلل من الحظر ويزيد من الإنتاجية.
\npackage main\n\nimport "fmt"\n\nfunc main() {\n ch := make(chan int, 3) // قناة بسعة تخزين مؤقت 3 عناصر\n ch <- 1\n ch <- 2\n ch <- 3\n // ch <- 4 // حظر، لأن المخزن المؤقت ممتلئ\n fmt.Println(<-ch)\n fmt.Println(<-ch)\n fmt.Println(<-ch)\n}\n\n3.2. القنوات أحادية الاتجاه (Unidirectional Channels)
\nلأمان الأنواع وتحديد دور القناة بوضوح، تُستخدم القنوات أحادية الاتجاه: chan<- (للإرسال فقط) و <-chan (للاستلام فقط).
package main\n\nimport "fmt"\n\nfunc sender(out chan<- int) {\n for i := 0; i < 5; i++ {\n out <- i\n }\n close(out)\n}\n\nfunc receiver(in <-chan int) {\n for val := range in {\n fmt.Println(val)\n }\n}\n\nfunc main() {\n ch := make(chan int)\n go sender(ch)\n receiver(ch)\n}\n\n3.3. العامل select
\nselect يسمح للغوروتين بانتظار عدة قنوات في نفس الوقت. هذا