لو عندك 50 goroutine بتكتب على نفس الـ map، الـ sync.Mutex هيشتغل لكن هيخلّي الكود هش. Channels في Go بتحل المشكلة بمنطق مختلف: بدل ما تقفل البيانات، تنقل ملكيتها. الفرق ده بيمنع category كاملة من الـ bugs، بس له ثمن واضح في الأداء هنقيسه بالظبط.
Go Channels: الأصل في التواصل بين Goroutines
المشكلة باختصار
Goroutine في Go رخيصة جدًا — ممكن تشغّل 100 ألف منها في دقيقة. لكن لحظة ما اتنين منهم يتلامسوا على نفس المتغيّر، عندك race condition. الحل التقليدي في C++/Java هو الـ mutex، والحل الأصلي في Go هو الـ channel. شعار فريق Go نفسه: "Don't communicate by sharing memory; share memory by communicating".
Unbuffered vs Buffered: الفرق الجوهري في سطرين
في Go نوعين من الـ channels، والفرق بينهم مش في الأداء بس — الفرق في متى بيحصل الـ synchronization.
- Unbuffered (
make(chan int)): المُرسِل بيتعلّق لحد ما المُستقبِل يستلم. ده بيضمن "handshake" — المُرسِل متأكد إن الرسالة وصلت. - Buffered (
make(chan int, 16)): المُرسِل بيكتب ويمشي لحد ما الـ buffer يمتلى. بيفكّ الترابط الزمني بين المُرسِل والمُستقبِل.
القاعدة اللي بستخدمها: unbuffered للـ coordination، buffered للـ throughput. لو محتاج تتأكد إن goroutine خلّصت شغلها قبل ما تكمل، unbuffered. لو عندك producer أسرع من consumer وعايز تمتص الفروقات، buffered بحجم = عدد الـ cores × 2 تقريبًا.
مثال تنفيذي: Worker Pool بـ 25 سطر
السيناريو: عندك 1000 URL محتاج تعمل لها HTTP request، متقدرش تفتح 1000 request مع بعض لأن الـ target بيعمل rate-limit عند 50 request/ثانية. Worker pool بـ 10 workers يحل المشكلة.
package main
import (
"fmt"
"net/http"
"sync"
)
func worker(id int, jobs <-chan string, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for url := range jobs {
resp, err := http.Get(url)
if err != nil {
results <- 0
continue
}
results <- resp.StatusCode
resp.Body.Close()
}
}
func main() {
urls := []string{"https://example.com", "https://go.dev" /* ...998 more */}
jobs := make(chan string, len(urls))
results := make(chan int, len(urls))
var wg sync.WaitGroup
for w := 1; w <= 10; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
for _, u := range urls { jobs <- u }
close(jobs)
wg.Wait()
close(results)
for r := range results { fmt.Println(r) }
}