التزامن وتعدد الخيوط في Go: من الألف إلى الياء

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

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

ابدأ الدورة

التزامن وتعدد الخيوط في Go: من الألف إلى الياء



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



1. الجوروتينات: خيوط خفيفة الوزن



الجوروتين (goroutine) هو دالة أو طريقة يتم تنفيذها بشكل متزامن مع جوروتينات أخرى. على عكس خيوط نظام التشغيل، فإن الجوروتينات لها تكاليف عامة ضئيلة: تبدأ مكدسها من 2-4 كيلوبايت ويمكن أن تنمو ديناميكيًا. تشغيل جوروتين بسيط للغاية - فقط أضف الكلمة المفتاحية go قبل استدعاء الدالة.



package main

import ( "fmt" "time")

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

func main() { go say("Hello from goroutine") say("Hello from main") // نعطي وقتًا للجوروتين للانتهاء time.Sleep(500 * time.Millisecond)}


لاحظ: ينتهي البرنامج فورًا بعد تنفيذ الدالة main()، دون انتظار انتهاء الجوروتينات المشغلة. للمزامنة نستخدم time.Sleep (في المشاريع الحقيقية نستخدم sync.WaitGroup أو القنوات).



2. القنوات: نقل آمن للبيانات



القنوات (channels) هي "أنابيب" محددة النوع تتبادل من خلالها الجوروتينات البيانات. تحل مشكلتين رئيسيتين: مزامنة التنفيذ ونقل القيم. القنوات إما مخزنة أو غير مخزنة.



قناة غير مخزنة

عملية الإرسال تحجب الجوروتين حتى تقوم جوروتين أخرى بالقراءة. هذا يضمن المزامنة "أثناء التنقل".



package main

import "fmt"

func main() { ch := make(chan string)

go func() { ch <- "مرحبًا من الجوروتين!" }()

msg := <-ch fmt.Println(msg)}


قناة مخزنة

القناة ذات المخزن تسمح بإرسال عدة قيم دون قراءة فورية، حتى يمتلئ المخزن.



package main

import "fmt"

func main() { ch := make(chan int, 2) ch <- 10 ch <- 20 // ch <- 30 // deadlock! المخزن ممتلئ

fmt.Println(<-ch) // 10 fmt.Println(<-ch) // 20}


Range و close

إغلاق القناة يشير للمستلمين أنه لن يكون هناك المزيد من البيانات. حلقة for range تنتهي تلقائيًا عند إغلاق القناة.



package main

import "fmt"

func producer(ch chan int) { for i := 1; i <= 5; i++ { ch <- i } close(ch)}

func main() { ch := make(chan int) go producer(ch)

for val := range ch { fmt.Println("تم الاستلام:", val) }}


3. المزامنة والميوتكسات



على الرغم من قوة القنوات، قد يكون الوصول المباشر للبيانات المشتركة ضروريًا في بعض الأحيان. توفر حزمة sync الميوتكسات (sync.Mutex) وميوتكسات القراءة (sync.RWMutex) لحماية الأقسام الحرجة.



package main

import ( "fmt" "sync")

var counter = 0var mu sync.Mutex

func increment(wg *sync.WaitGroup) { defer wg.Done() mu.Lock() counter++ mu.Unlock()}

func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go increment(&wg) } wg.Wait() fmt.Println("العداد النهائي:", counter)}


هنا، sync.WaitGroup ينتظر انتهاء جميع الجوروتينات، والميوتكس يمنع حالة السباق (data race). بدون الميوتكس، ستكون النتيجة غير متوقعة.



4. أنماط التزامن

المدونات

توصيات الكتب