Goku

همزمانی (Concurrency) در زبان برنامه‌نویسی Go

همزمانی یکی از نقاط قوت اصلی Go است که آن را برای ساخت سیستم‌های مقیاس‌پذیر و کارآمد ایده‌آل می‌کند. Go از goroutines (واحدهای سبک اجرای موازی) و channels (ابزارهای ارتباطی بین goroutine‌ها) برای مدیریت همزمانی استفاده می‌کند. این جزوه تمام جنبه‌های همزمانی در Go را با جزئیات کامل پوشش می‌دهد.


1. Goroutines

1.1. مفهوم

Goroutine یک تابع است که به صورت موازی و مستقل از تابع اصلی اجرا می‌شود. برخلاف thread‌های سیستم‌عامل که سنگین هستند، goroutine‌ها سبک‌وزن‌اند (چند کیلوبایت حافظه) و توسط runtime Go مدیریت می‌شوند.

1.2. ایجاد Goroutine

برای ایجاد یک goroutine، کافی است کلمه کلیدی go را قبل از فراخوانی تابع قرار دهید:

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    for i := 0; i < 3; i++ {
        fmt.Println("Hello from goroutine")
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go sayHello() // اجرای موازی
    for i := 0; i < 3; i++ {
        fmt.Println("Hello from main")
        time.Sleep(100 * time.Millisecond)
    }
    time.Sleep(1 * time.Second) // منتظر اتمام goroutine
}

1.3. مدیریت Goroutine‌ها

1.4. نکات پیشرفته

1.5. خطاهای رایج


2. Channels

2.1. مفهوم

کانال‌ها (Channels) ابزار اصلی برای ارتباط و هماهنگی بین goroutine‌ها هستند. آن‌ها امکان ارسال و دریافت داده‌ها را به صورت ایمن فراهم می‌کنند.

2.2. انواع کانال

2.3. ارسال/دریافت داده

مثال

package main

import "fmt"

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

    go func() {
        ch <- "Hello from goroutine"
        close(ch)
    }()

    msg := <-ch
    fmt.Println(msg) // Hello from goroutine
}

2.4. نکات پیشرفته

2.5. خطاهای رایج


3. Select

3.1. مفهوم

دستور select برای مدیریت چندین کانال به صورت همزمان استفاده می‌شود. شبیه switch است، اما برای عملیات کانال‌ها.

3.2. سینتکس

select {
case v := <-ch1:
    fmt.Println("Received from ch1:", v)
case ch2 <- x:
    fmt.Println("Sent to ch2")
default:
    fmt.Println("No operation")
}

3.3. الگوهای تایم‌اوت و لغو

3.4. مثال

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "from ch1"
    }()
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "from ch2"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

3.5. نکات پیشرفته

3.6. خطاهای رایج


4. الگوهای Concurrency

Go از الگوهای مختلفی برای حل مسائل همزمانی استفاده می‌کند. در ادامه، الگوهای مهم شرح داده می‌شوند.

4.1. Worker Pool

مفهوم

Worker Pool مجموعه‌ای از goroutine‌ها (کارگران) است که وظایف را از یک کانال دریافت کرده و پردازش می‌کنند.

مثال

package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    // راه‌اندازی 3 کارگر
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go func(wid int) {
            defer wg.Done()
            worker(wid, jobs, results)
        }(w)
    }

    // ارسال کارها
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // جمع‌آوری نتایج
    go func() {
        wg.Wait()
        close(results)
    }()

    for r := range results {
        fmt.Println("Result:", r)
    }
}

نکات

4.2. Fan-out/Fan-in

مفهوم

مثال

package main

import (
    "fmt"
    "sync"
)

func producer(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

func merge(cs ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    wg.Add(len(cs))
    for _, c := range cs {
        go func(ch <-chan int) {
            defer wg.Done()
            for n := range ch {
                out <- n
            }
        }(c)
    }
    go func() {
        wg.Wait()
        close(out)
    }()
    return out
}

func main() {
    in := producer(1, 2, 3, 4)
    c1 := square(in)
    c2 := square(in) // Fan-out: دو goroutine برای محاسبه مربع
    out := merge(c1, c2) // Fan-in: جمع‌آوری نتایج
    for n := range out {
        fmt.Println(n)
    }
}

نکات

4.3. Pipeline

مفهوم

Pipeline مجموعه‌ای از مراحل پردازش است که هر مرحله خروجی خود را به مرحله بعدی ارسال می‌کند.

مثال

package main

import "fmt"

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    for n := range square(gen(2, 3, 4)) {
        fmt.Println(n) // 4, 9, 16
    }
}

نکات

4.4. Generator

مفهوم

Generator یک goroutine است که مقادیر را به صورت تدریجی تولید می‌کند.

مثال

package main

import (
    "fmt"
    "time"
)

func generator() <-chan int {
    out := make(chan int)
    go func() {
        for i := 1; ; i++ {
            out <- i
            time.Sleep(500 * time.Millisecond)
        }
    }()
    return out
}

func main() {
    nums := generator()
    for i := 0; i < 5; i++ {
        fmt.Println(<-nums)
    }
}

نکات


5. مدیریت منابع

5.1. پکیج sync

WaitGroup

برای انتظار اتمام مجموعه‌ای از goroutine‌ها:

var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    // کار
}()
wg.Wait()

Mutex

برای جلوگیری از دسترسی همزمان به منابع مشترک:

var (
    counter int
    mu      sync.Mutex
)

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

RWMutex

برای خواندن/نوشتن همزمان:

var (
    data  map[string]int
    rwmu  sync.RWMutex
)

func read(key string) int {
    rwmu.RLock()
    defer rwmu.RUnlock()
    return data[key]
}

5.2. پکیج atomic

برای عملیات اتمیک بدون قفل:

var counter int64
atomic.AddInt64(&counter, 1)
fmt.Println(atomic.LoadInt64(&counter)) // 1

5.3. نکات

5.4. خطاهای رایج


6. Context

6.1. مفهوم

پکیج context برای مدیریت لغو، مهلت زمانی، و انتقال داده بین goroutine‌ها استفاده می‌شود.

6.2. انواع Context

6.3. انتقال داده

ctx = context.WithValue(ctx, "userID", 123)
userID := ctx.Value("userID").(int)

6.4. مثال

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    go func() {
        select {
        case <-time.After(3 * time.Second):
            fmt.Println("Work completed")
        case <-ctx.Done():
            fmt.Println("Work cancelled:", ctx.Err())
        }
    }()

    time.Sleep(3 * time.Second)
}

6.5. نکات پیشرفته

6.6. خطاهای رایج


7. نکات پیشرفته Concurrency

7.1. جلوگیری از Data Race

7.2. پروفایلینگ

7.3. بهینه‌سازی

7.4. خطاهای رایج


8. نتیجه‌گیری

این جزوه تمام جنبه‌های همزمانی در Go را با جزئیات کامل پوشش داد. از مفاهیم پایه مثل goroutine‌ها و کانال‌ها تا الگوهای پیشرفته مثل Worker Pool و Pipeline، و ابزارهای مدیریت منابع مثل Context و sync، هر بخش با مثال‌های عملی و نکات پیشرفته ارائه شد. برای یادگیری عمیق‌تر: