التزامن وتعدد الخيوط في Go: المستوى المتقدم
تم تصميم Go (Golang) منذ البداية كلغة لكتابة تطبيقات قابلة للتوسع وعالية الأداء. يعتمد ذلك على نموذج تزامن قوي، قائم على الغوروتينات والقنوات. إذا كنت قد أتقنت بالفعل الأمثلة الأساسية مع go func() والقنوات البسيطة، فقد حان الوقت للانتقال إلى المستوى التالي. في هذه المقالة، سنستعرض الأنماط المتقدمة للعمل مع التزامن في Go، والتي ستساعدك على كتابة كود موثوق وفعال وآمن.
1. إدارة دورة حياة الغوروتينات: Context و WaitGroup
من أكثر المشاكل شيوعًا تسرب الغوروتينات. الغوروتينة التي تنتظر بيانات من قناة إلى أجل غير مسمى لن تنتهي أبدًا إذا تم إغلاق القناة بشكل غير صحيح. لإدارة دورة حياة الغوروتينات في Go، هناك أداتان رئيسيتان: sync.WaitGroup و context.Context.
sync.WaitGroup لانتظار الانتهاء
استخدم sync.WaitGroup عندما تحتاج إلى انتظار انتهاء مجموعة ثابتة من الغوروتينات. هذه آلية بسيطة وفعالة.
package main
import (\t"fmt"\t"sync"\t"time")
func worker(id int, wg *sync.WaitGroup) {\tdefer wg.Done() // تقليل العداد عند الخروج\tfmt.Printf("العامل %d بدأ العمل", id)\ttime.Sleep(time.Second)\tfmt.Printf("العامل %d أنهى العمل", id)}
func main() {\tvar wg sync.WaitGroup
\tfor i := 1; i <= 3; i++ {\t\twg.Add(1) // زيادة العداد قبل التشغيل\t\tgo worker(i, &wg)\t}
\twg.Wait() // الحظر حتى يصبح العداد صفرًا\tfmt.Println("جميع العمال انتهوا")}context.Context للإلغاء والمواعيد النهائية
عندما تعمل الغوروتينة في حلقة أو تنفذ عملية طويلة، يجب أن تكون قادرة على الانتهاء بشكل صحيح بناءً على إشارة من الخارج. context.Context هي الطريقة القياسية لنقل إشارة الإلغاء أو الموعد النهائي أو المهلة.
package main
import (\t"context"\t"fmt"\t"time")
func longRunningTask(ctx context.Context) {\tfor {\t\tselect {\t\tcase <-ctx.Done():\t\t\tfmt.Println("تلقت الغوروتينة إشارة التوقف")\t\t\treturn\t\tdefault:\t\t\ttime.Sleep(500 * time.Millisecond)\t\t\tfmt.Println("أعمل...")\t\t}\t}}
func main() {\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\tdefer cancel() // من المهم استدعاء cancel لتحرير الموارد
\tgo longRunningTask(ctx)
\ttime.Sleep(3 * time.Second)\tfmt.Println("انتهى Main")}2. القنوات: التخزين المؤقت، الإغلاق، ونمط Fan-Out/Fan-In
القنوات هي الطريقة الرئيسية للمزامنة ونقل البيانات بين الغوروتينات. على المستوى المتقدم، من المهم فهم الفرق بين القنوات المخزنة مؤقتًا وغير المخزنة مؤقتًا، وإغلاقها بشكل صحيح، واستخدام أنماط التجميع.
القنوات المخزنة مؤقتًا مقابل غير المخزنة مؤقتًا
القناة غير المخزنة مؤقتًا (بدون مخزن مؤقت) تمنع المرسل حتى يقرأ المستقبل البيانات. القناة المخزنة مؤقتًا تسمح بإرسال عدة رسائل دون انتظار، حتى يمتلئ المخزن المؤقت. يعتمد الاختيار على المهمة: للمزامنة الصارمة استخدم غير المخزنة مؤقتًا، ولتخفيف الحمل استخدم المخزنة مؤقتًا.
نمط Fan-Out/Fan-In
هذا أحد أقوى الأنماط. Fan-Out هو توزيع المهام على عدة غوروتينات (عمال). Fan-In هو جمع النتائج من عدة غوروتينات في قناة واحدة.
package main
import (\t"fmt"\t"sync")
func producer(nums ...int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor _, n := range nums {\t\t\tout <- n\t\t}\t\tclose(out)\t}()\treturn out}
func square(in <-chan int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor n := range in {\t\t\tout <- n * n\t\t}\t\tclose(out)\t}()\treturn out}
func merge(channels ...<-chan int) <-chan int {\t