البرمجة غير المتزامنة في Go: دليل كامل مع أمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا نحتاج البرمجة غير المتزامنة في Go؟



Go (Golang) صُمم منذ البداية ليكون فعالاً في التعامل مع التزامن. على عكس العديد من اللغات التي تنفذ عدم التزامن من خلال الاستدعاءات المعقدة (callbacks) أو الوعود (promises)، يقدم Go نموذجاً أنيقاً يعتمد على الغوروتينات (goroutines) والقنوات (channels). هذا النموذج يسمح بكتابة كود غير متزامن يُقرأ كما لو كان متزامناً، لكنه يُنفذ بالتوازي، مستغلاً بأقصى قدرة موارد المعالجات متعددة النوى.



في هذا الدليل، سنشرح جميع المفاهيم الأساسية للبرمجة غير المتزامنة في Go: بدءاً من إنشاء الغوروتينات وصولاً إلى أنماط التزامن المعقدة باستخدام القنوات وأدوات الحزمة sync. ستكون المادة مفيدة لكل من المبتدئين الذين يتعلمون Go، والمطورين ذوي الخبرة الذين يرغبون في تنظيم معرفتهم.



1. الغوروتينات (Goroutines): خيوط خفيفة الوزن



الغوروتين هو خيط تنفيذ خفيف الوزن يُدار بواسطة بيئة تشغيل Go. على عكس خيوط نظام التشغيل، تمتلك الغوروتينات مكدساً صغيراً جداً (يبدأ ببضعة كيلوبايتات) يمكن أن ينمو ويتقلص ديناميكياً. هذا يسمح بتشغيل عشرات الآلاف من الغوروتينات في وقت واحد دون استهلاك كبير للذاكرة.



تشغيل غوروتين

لتشغيل دالة كغوروتين، يكفي إضافة الكلمة المفتاحية go قبل استدعاء الدالة:



package main

import ( "fmt" "time")

func printNumbers() { for i := 1; i <= 5; i++ { fmt.Println(i) time.Sleep(100 * time.Millisecond) }}

func main() { // تشغيل الدالة كغوروتين go printNumbers()

// الغوروتين الرئيسي يستمر في العمل fmt.Println("الغوروتين الرئيسي يعمل")

// إعطاء وقت لتنفيذ الغوروتين الفرعي time.Sleep(1 * time.Second) fmt.Println("تم إنهاء البرنامج")}


هام: ينتهي البرنامج عند انتهاء الدالة main(). إذا انتهى الغوروتين الرئيسي مبكراً، سيتم إيقاف جميع الغوروتينات الفرعية قسراً. لذلك في المثال استخدمنا time.Sleep للانتظار — عملياً، تُستخدم القنوات أو sync.WaitGroup للمزامنة.



الدوال المجهولة في الغوروتينات

غالباً ما تُنشأ الغوروتينات باستخدام دوال مجهولة:



go func() {    fmt.Println("يتم التنفيذ في غوروتين")}()


2. القنوات (Channels): التواصل بين الغوروتينات



القنوات هي "أنابيب" محددة النوع يمكن للغوروتينات من خلالها إرسال واستقبال القيم. هذه هي الآلية الأساسية للمزامنة وتبادل البيانات في Go. توفر القنوات وصولاً آمناً إلى البيانات دون استخدام أقفال صريحة (mutexes).



إنشاء واستخدام قناة

تُنشأ القناة باستخدام الدالة make(). يُستخدم العامل <- لإرسال واستقبال البيانات:



package main

import "fmt"

func sum(s []int, c chan int) { total := 0 for _, v := range s { total += v } c <- total // إرسال النتيجة إلى القناة}

func main() { s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c)

x, y := <-c, <-c // استقبال البيانات من القناة

fmt.Println(x, y, x+y)}


في هذا المثال، نشغل غوروتينين، كل منهما يحسب مجموع نصف الشريحة (slice). تُرسل النتائج إلى القناة، ويستقبلها الغوروتين الرئيسي ويطبع المجموع الكلي.



القنوات المخزنة (Buffered Channels)

افتراضياً، القنوات غير مخزنة — يُحظر الإرسال حتى يصبح المستقبل جاهزاً لاستقبال القيمة. القنوات المخزنة تمتلك طابوراً بحجم محدد:



ch := make(chan int, 2) // قناة بمخزن يتسع لعنصرينch <- 1ch <- 2// ch <- 3 // خطأ: deadlock، المخزن ممتلئfmt.Println(<-ch) // 1fmt.Println(<-ch) // 2

المدونات

توصيات الكتب