يعد نموذج التزامن الخاص بـ Go — goroutines والقنوات — هو أبسط وأقوى نظام تزامن في أي لغة سائدة. في عام 2026، يعمل الإصدار 1.23 من Go مع مكررات النطاق فوق الوظيفة والتسجيل المنظم للسجل على جعل كود Go المتزامن أكثر نظافة من أي وقت مضى. يغطي هذا الدليل كل شيء بدءًا من الإجراءات الأساسية وحتى أنماط الإنتاج.
📋 Table of Contents
Goroutines – خيوط خفيفة الوزن
package main
import (
"fmt"
"sync"
"time"
)
func main() {
// goroutine = lightweight thread (starts with 2KB stack, not 1MB like OS threads)
go sayHello("World") // fire and forget
// Wait for goroutine using WaitGroup
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Worker %d done
", id)
time.Sleep(time.Duration(id) * time.Millisecond)
}(i)
}
wg.Wait() // block until all goroutines finish
fmt.Println("All workers done")
}
func sayHello(name string) {
time.Sleep(10 * time.Millisecond)
fmt.Printf("Hello, %s!
", name)
}
القنوات – التواصل بين Goroutines
package main
import "fmt"
func main() {
// Unbuffered channel — sender blocks until receiver ready
ch := make(chan int)
go func() {
ch <- 42 // send
}()
value := <-ch // receive
fmt.Println(value) // 42
// Buffered channel — sender blocks only when buffer full
buffered := make(chan string, 5)
buffered <- "hello"
buffered <- "world"
// Doesn't block because buffer has space
close(buffered)
// Range over closed channel
for msg := range buffered {
fmt.Println(msg)
}
// Directional channels in function signatures
produce := func(ch chan<- int) { // send-only
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}
consume := func(ch <-chan int) { // receive-only
for n := range ch {
fmt.Println("received:", n)
}
}
dataCh := make(chan int, 10)
go produce(dataCh)
consume(dataCh)
}
حدد — تعدد القنوات
package main
import (
"context"
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(200 * time.Millisecond)
ch2 <- "from ch2"
}()
// Select picks whichever channel is ready first
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
select {
case result := <-fetchData():
fmt.Println(result)
case <-ctx.Done():
fmt.Println("timeout:", ctx.Err())
}
// Non-blocking send with default
ch := make(chan int, 1)
select {
case ch <- 42:
fmt.Println("sent")
default:
fmt.Println("channel full, skipped")
}
}
نمط تجمع العمال
package main
import (
"fmt"
"sync"
)
type Job struct {
ID int
Data string
}
type Result struct {
JobID int
Output string
Err error
}
func workerPool(numWorkers int, jobs <-chan Job, results chan<- Result) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for job := range jobs {
output, err := processJob(job)
results <- Result{JobID: job.ID, Output: output, Err: err}
}
}(i)
}
// Close results channel when all workers done
go func() {
wg.Wait()
close(results)
}()
}
func processJob(job Job) (string, error) {
return fmt.Sprintf("processed: %s", job.Data), nil
}
func main() {
jobs := make(chan Job, 100)
results := make(chan Result, 100)
// Start 5 workers
go workerPool(5, jobs, results)
// Send 20 jobs
go func() {
for i := 0; i < 20; i++ {
jobs <- Job{ID: i, Data: fmt.Sprintf("item-%d", i)}
}
close(jobs)
}()
// Collect results
for result := range results {
if result.Err != nil {
fmt.Printf("Job %d failed: %v
", result.JobID, result.Err)
continue
}
fmt.Printf("Job %d: %s
", result.JobID, result.Output)
}
}
السياق – الإلغاء والمواعيد النهائية
package main
import (
"context"
"fmt"
"time"
)
func fetchWithContext(ctx context.Context, url string) (string, error) {
// Check if context already cancelled
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
// Simulate slow operation
done := make(chan string, 1)
go func() {
time.Sleep(500 * time.Millisecond)
done <- "data from " + url
}()
select {
case data := <-done:
return data, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
// Context with deadline
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
data, err := fetchWithContext(ctx, "https://api.example.com/data")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(data)
// Pass context to database queries (Go standard pattern)
// db.QueryContext(ctx, "SELECT ...")
// http.NewRequestWithContext(ctx, "GET", url, nil)
}
حزمة المزامنة – كائنات المزامنة ومرة واحدة
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// Mutex for shared state
type SafeCounter struct {
mu sync.RWMutex
count map[string]int
}
func (c *SafeCounter) Increment(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.count[key]++
}
func (c *SafeCounter) Get(key string) int {
c.mu.RLock() // multiple readers allowed simultaneously
defer c.mu.RUnlock()
return c.count[key]
}
// sync.Once — run initialization exactly once
var (
dbOnce sync.Once
dbInstance *Database
)
func GetDB() *Database {
dbOnce.Do(func() {
dbInstance = &Database{} // initialized exactly once, even concurrent calls
})
return dbInstance
}
// Atomic operations — lockless for simple counters
var requestCount int64
func handleRequest() {
atomic.AddInt64(&requestCount, 1)
// Process request...
}
func getRequestCount() int64 {
return atomic.LoadInt64(&requestCount)
}
errgroup — الأخطاء المتزامنة
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
ctx := context.Background()
// Run goroutines, collect first error
g, ctx := errgroup.WithContext(ctx)
var user, posts, stats interface{}
g.Go(func() error {
var err error
user, err = fetchUser(ctx, 1)
return err
})
g.Go(func() error {
var err error
posts, err = fetchPosts(ctx, 1)
return err
})
g.Go(func() error {
var err error
stats, err = fetchStats(ctx, 1)
return err
})
// Wait for all goroutines
if err := g.Wait(); err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(user, posts, stats)
}
يعد التزامن في عام 2026 أمرًا أنيقًا وقويًا عندما تتبع الشعار: “شارك الذاكرة من خلال التواصل، ولا تتواصل من خلال مشاركة الذاكرة”. استخدم goroutines بحرية (تكلفة كل منها حوالي 2 كيلو بايت)، والتنسيق مع القنوات وWaitGroups، والتعامل مع إلغاء السياق في جميع العمليات طويلة الأمد، وتجنب الحالة القابلة للتغيير المشتركة (تفضيل القنوات أو العمليات الذرية).
🔗 Share this article
✍️ Leave a Comment