مقدمة في التزامن في Go
لغة Go (Golang) تم تطويرها في Google مع مراعاة المتطلبات الحديثة للأداء وقابلية التوسع. واحدة من الميزات الرئيسية في Go هي الدعم المدمج لـ التزامن (concurrency) — قدرة البرنامج على تنفيذ مهام متعددة في وقت واحد. على عكس تعدد الخيوط التقليدي، حيث تُدار الخيوط بواسطة نظام التشغيل، تقدم Go goroutines خفيفة الوزن وأدوات مزامنة مريحة — القنوات.
في هذه المقالة سنشرح:
- ما هي goroutines وكيفية تشغيلها
- القنوات لتبادل البيانات بين goroutines
- الميوتكس (Mutexes) للوصول الآمن إلى الموارد المشتركة
- أمثلة عملية وأنماط للكود المتزامن
1. Goroutines: خيوط Go الخفيفة
Goroutine هي دالة تُنفذ بشكل متزامن مع goroutines أخرى. تشغل بضعة كيلوبايتات فقط من المكدس (على عكس خيوط نظام التشغيل، حيث يمكن أن يكون المكدس ميغابايتاً). يمكن تشغيل goroutine ببساطة عن طريق إضافة الكلمة المفتاحية go قبل استدعاء الدالة.
مثال: تشغيل goroutines
package main
import ( "fmt" "time")
func sayHello(name string) { for i := 0; i < 3; i++ { fmt.Printf("مرحباً، %s! (التكرار %d)", name, i) time.Sleep(100 * time.Millisecond) }}
func main() { go sayHello("أليس") // تشغيل goroutine go sayHello("بوب") // goroutine أخرى time.Sleep(1 * time.Second) // ننتظر الانتهاء fmt.Println("الدالة الرئيسية انتهت")}مهم: ينتهي البرنامج عندما تنتهي الدالة main، حتى لو كانت goroutines أخرى لا تزال تعمل. لذلك نستخدم time.Sleep للانتظار. في الكود الحقيقي، من الأفضل استخدام sync.WaitGroup.
استخدام sync.WaitGroup
package main
import ( "fmt" "sync")
func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // نقلل العداد عند الانتهاء fmt.Printf("العامل %d بدأ العمل", id) // ... بعض العمل ... fmt.Printf("العامل %d أنهى العمل", id)}
func main() { var wg sync.WaitGroup
for i := 1; i <= 3; i++ { wg.Add(1) // نزيد العداد قبل تشغيل goroutine go worker(i, &wg) }
wg.Wait() // نمنع main حتى تنتهي جميع goroutines fmt.Println("جميع العمال أنهوا العمل")}2. القنوات: تبادل البيانات بين goroutines
القنوات (channels) هي "أنابيب" محددة النوع، يمكن من خلالها للـ goroutines إرسال واستقبال القيم. توفر القنوات مزامنة آمنة دون أقفال صريحة.
إنشاء واستخدام القنوات
package main
import "fmt"
func main() { // ننشئ قناة للأعداد الصحيحة ch := make(chan int)
// نشغل goroutine ترسل البيانات go func() { ch <- 42 // نرسل القيمة إلى القناة }()
// نستقبل القيمة من القناة value := <-ch fmt.Println("تم الاستلام:", value)}افتراضياً، القنوات غير مخزنة مؤقتاً: الإرسال يُحظر حتى يصبح المستقبل جاهزاً لاستقبال البيانات، والعكس صحيح. هذا يضمن المزامنة.
القنوات المخزنة مؤقتاً
package main
import "fmt"
func main() { // قناة بمخزن يتسع لـ 3 عناصر ch := make(chan string, 3)
// نرسل دون حظر (طالما المخزن غير ممتلئ) ch <- "واحد" ch <- "اثنان" ch <- "ثلاثة"
// نقرأ fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch)}إغلاق القنوات و range
package main
import "fmt"
func fibonacci(n int, ch chan int) { a, b := 0, 1 for i := 0; i < n; i++ { ch <-