آموزش Channel در گولنگ - کانال ارتباطی بین گوروتینها
2025/11/24Channel راه اصلی ارتباط امن بین 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 |