GoCasts آموزش Go به زبان ساده

بیش از ۱۰۰۰ شرکت‌کننده یادگیری Go و Backend رو از امروز شروع کن
ثبت‌نام دوره + تیم‌سازی

آموزش Channel در گولنگ - کانال ارتباطی بین گوروتین‌ها

Channel راه اصلی ارتباط امن بین Goroutine‌ها در Go است. در این مقاله، Channel‌ها را به صورت عمیق بررسی می‌کنیم.

Channel چیست؟

Channel یک کانال ارتباطی است که:

  • داده بین Goroutine‌ها منتقل می‌کند
  • همزمان‌سازی فراهم می‌کند
  • از race condition جلوگیری می‌کند

فلسفه Go: “داده را با برقراری ارتباط به اشتراک بگذارید، نه حافظه را با اشتراک‌گذاری.”

ایجاد Channel

package main

import "fmt"

func main() {
    // Channel بدون بافر
    ch := make(chan int)

    // Channel با بافر (ظرفیت 3)
    bufferedCh := make(chan string, 3)

    // Channel فقط برای ارسال
    var sendOnly chan<- int = ch

    // Channel فقط برای دریافت
    var receiveOnly <-chan int = ch

    fmt.Printf("ch type: %T\n", ch)
    fmt.Printf("bufferedCh type: %T\n", bufferedCh)
}

ارسال و دریافت

package main

import "fmt"

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

    // ارسال در Goroutine
    go func() {
        ch <- "سلام از Goroutine!"  // ارسال
    }()

    // دریافت در main
    message := <-ch  // دریافت
    fmt.Println(message)
}

Channel بدون بافر vs با بافر

بدون بافر (Unbuffered)

// ارسال‌کننده بلاک می‌شود تا دریافت‌کننده آماده شود
ch := make(chan int)

go func() {
    ch <- 42  // بلاک تا دریافت
    fmt.Println("ارسال شد")
}()

value := <-ch  // بلاک تا ارسال
fmt.Println("دریافت:", value)

با بافر (Buffered)

// ارسال بلاک نمی‌شود تا بافر پر شود
ch := make(chan int, 3)

ch <- 1  // بدون بلاک
ch <- 2  // بدون بلاک
ch <- 3  // بدون بلاک
// ch <- 4  // بلاک! بافر پر است

fmt.Println(<-ch)  // 1
fmt.Println(<-ch)  // 2
fmt.Println(<-ch)  // 3

بستن Channel

package main

import "fmt"

func producer(ch chan int) {
    for i := 1; i <= 5; i++ {
        ch <- i
    }
    close(ch)  // بستن channel
}

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

    go producer(ch)

    // روش 1: range
    for value := range ch {
        fmt.Println(value)
    }

    // روش 2: بررسی بسته بودن
    ch2 := make(chan int)
    go func() {
        ch2 <- 42
        close(ch2)
    }()

    value, ok := <-ch2
    fmt.Println(value, ok)  // 42 true

    value, ok = <-ch2
    fmt.Println(value, ok)  // 0 false (بسته شده)
}

نکته: فقط ارسال‌کننده باید Channel را ببندد، نه دریافت‌کننده.

Select

select برای کار با چندین Channel استفاده می‌شود:

package main

import (
    "fmt"
    "time"
)

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

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "پیام از ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "پیام از ch2"
    }()

    // دریافت از هر کدام که زودتر آماده شود
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

Select با default

select {
case msg := <-ch:
    fmt.Println(msg)
default:
    // اگر هیچ channel آماده نبود
    fmt.Println("هیچ پیامی نیست")
}

Select با Timeout

package main

import (
    "fmt"
    "time"
)

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

    go func() {
        time.Sleep(2 * time.Second)
        ch <- "پاسخ"
    }()

    select {
    case msg := <-ch:
        fmt.Println("دریافت:", msg)
    case <-time.After(1 * time.Second):
        fmt.Println("Timeout!")
    }
}

الگوهای رایج

۱. Generator Pattern

func fibonacci(n int) <-chan int {
    ch := make(chan int)

    go func() {
        defer close(ch)
        a, b := 0, 1
        for i := 0; i < n; i++ {
            ch <- a
            a, b = b, a+b
        }
    }()

    return ch
}

func main() {
    for num := range fibonacci(10) {
        fmt.Println(num)
    }
}

۲. Fan-Out / Fan-In

package main

import (
    "fmt"
    "sync"
)

// تابع پردازش
func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2
    }
}

// Fan-Out: توزیع کار بین worker ها
// Fan-In: جمع‌آوری نتایج
func main() {
    numJobs := 10
    numWorkers := 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    // راه‌اندازی worker ها (Fan-Out)
    var wg sync.WaitGroup
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            worker(id, jobs, results)
        }(w)
    }

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

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

    for result := range results {
        fmt.Println("نتیجه:", result)
    }
}

۳. Pipeline Pattern

package main

import "fmt"

// مرحله 1: تولید اعداد
func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

// مرحله 2: توان دوم
func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

// مرحله 3: جمع
func sum(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        total := 0
        for n := range in {
            total += n
        }
        out <- total
        close(out)
    }()
    return out
}

func main() {
    // Pipeline: generate -> square -> sum
    numbers := generate(1, 2, 3, 4, 5)
    squared := square(numbers)
    result := sum(squared)

    fmt.Println("مجموع توان دوم:", <-result)  // 55
}

۴. Semaphore با Channel

package main

import (
    "fmt"
    "time"
)

func main() {
    // محدود کردن همزمانی به 3
    semaphore := make(chan struct{}, 3)

    for i := 1; i <= 10; i++ {
        semaphore <- struct{}{}  // گرفتن اجازه

        go func(id int) {
            defer func() { <-semaphore }()  // آزاد کردن

            fmt.Printf("کار %d شروع شد\n", id)
            time.Sleep(time.Second)
            fmt.Printf("کار %d تمام شد\n", id)
        }(i)
    }

    // صبر برای اتمام
    time.Sleep(5 * time.Second)
}

۵. Done Channel برای لغو

package main

import (
    "fmt"
    "time"
)

func worker(done <-chan struct{}, results chan<- int) {
    for i := 1; ; i++ {
        select {
        case <-done:
            fmt.Println("Worker متوقف شد")
            return
        case results <- i:
            time.Sleep(100 * time.Millisecond)
        }
    }
}

func main() {
    done := make(chan struct{})
    results := make(chan int)

    go worker(done, results)

    // دریافت 5 نتیجه
    for i := 0; i < 5; i++ {
        fmt.Println(<-results)
    }

    // توقف worker
    close(done)
    time.Sleep(200 * time.Millisecond)
}

نکات مهم

۱. Deadlock

// Deadlock! - main منتظر است ولی کسی ارسال نمی‌کند
func main() {
    ch := make(chan int)
    value := <-ch  // بلاک برای همیشه
}

// همچنین Deadlock!
func main() {
    ch := make(chan int)
    ch <- 42  // بلاک - کسی دریافت نمی‌کند
}

۲. nil Channel

var ch chan int  // nil

// ارسال به nil channel: بلاک برای همیشه
// ch <- 42

// دریافت از nil channel: بلاک برای همیشه
// <-ch

// بستن nil channel: panic!
// close(ch)

۳. ارسال به Channel بسته

ch := make(chan int)
close(ch)

ch <- 42  // panic: send on closed channel

جدول رفتار Channel

عملیات nil Channel Channel باز Channel بسته
ارسال بلاک موفق/بلاک panic
دریافت بلاک موفق/بلاک مقدار صفر
بستن panic موفق panic

جمع‌بندی

مفهوم سینتکس کاربرد
ایجاد make(chan T) Channel بدون بافر
ایجاد با بافر make(chan T, n) Channel با ظرفیت n
ارسال ch <- value فرستادن داده
دریافت <-ch گرفتن داده
بستن close(ch) اعلام پایان
range for v := range ch پیمایش تا بسته شدن
select select { case ... } چند channel

قدم‌های بعدی

منابع

بیش از ۱۰۰۰ شرکت‌کننده یادگیری Go و Backend رو از امروز شروع کن
ثبت‌نام دوره + تیم‌سازی