التزامن وتعدد الخيوط في Go: دليل شامل للمطورين

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

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

ابدأ الدورة

التزامن وتعدد الخيوط في Go: من الـ Goroutines إلى الـ Channels

لغة Go (Golang) تم تطويرها في Google مع مراعاة متطلبات الأداء وقابلية التوسع الحديثة. ميزتها الرئيسية هي الدعم المدمج للتزامن، والذي تم تنفيذه على مستوى اللغة، وليس من خلال مكتبات خارجية. على عكس Python، حيث تعدد الخيوط محدود بـ GIL (Global Interpreter Lock)، تقدم Go الـ Goroutines خفيفة الوزن التي تسمح بتنفيذ آلاف المهام بكفاءة في وقت واحد. في هذه المقالة، سنقوم بتحليل المفاهيم الأساسية للتزامن وتعدد الخيوط في Go، بما في ذلك الـ Goroutines، والـ Channels، والـ Mutexes، و WaitGroup، مع أمثلة عملية للكود.



1. الـ Goroutines: خيوط Go خفيفة الوزن

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



package main

import ( "fmt" "time")

func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) }}

func main() { go say("Hello") // تشغيل Goroutine say("World") // استدعاء عادي في الخيط الرئيسي}


في هذا المثال، يتم تنفيذ say("Hello") في Goroutine منفصلة، بينما say("World") في الخيط الرئيسي. سيكون الإخراج متداخلاً، حيث تعمل كلتا الدالتين بشكل متزامن. من المهم أن نتذكر: إذا انتهت الدالة الرئيسية main، سيتم إيقاف جميع الـ Goroutines قسراً.



2. الـ Channels: نقل آمن للبيانات بين الـ Goroutines

الـ Channels هي "أنابيب" محددة النوع، تتواصل من خلالها الـ Goroutines مع بعضها البعض. توفر التزامن دون استخدام صريح للـ Mutexes. الـ Channels تكون إما مخزنة (بسعة) أو غير مخزنة (متزامنة).



Channel غير مخزن

package main

import "fmt"

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

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 // استقبال من الـ Channel

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


هنا، تقوم Goroutine بحساب مجموع نصفي المصفوفة وإرسال النتائج إلى الـ Channel. يتم حظر الـ Goroutine الرئيسي حتى استلام القيمتين. يعمل الـ Channel غير المخزن كنقطة تزامن: يتم حظر الإرسال حتى يصبح المستقبل جاهزاً، والعكس صحيح.



Channel مخزن

package main

import "fmt"

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


الـ Channel المخزن يسمح بإرسال البيانات دون حظر، طالما أن المخزن لم يمتلئ. هذا مفيد عندما يكون سرعة الإرسال أعلى من سرعة المعالجة.



3. التزامن: WaitGroup والـ Mutexes

على الرغم من أن الـ Channels تغطي معظم السيناريوهات، إلا أنه في بعض الأحيان نحتاج إلى تحكم أدق في التزامن. لهذا، توجد في حزمة sync أدوات WaitGroup (انتظار اكتمال مجموعة من الـ Goroutines) و Mutex (الاستبعاد المتبادل لحماية البيانات المشتركة).



WaitGroup: انتظار جميع الـ Goroutines

package main

import ( "fmt" "sync")

func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // تقليل العداد عند الانتهاء fmt.Printf("Worker %d starting", id) // محاكاة العمل fmt.Printf("Worker %d done", id)}

func main() { var wg sync.WaitGroup

for i := 1; i <= 3; i++ { wg.Add(1) // زيادة العداد

المدونات

توصيات الكتب