# Go Channel

Channel 是 Go 并发编程中 goroutine 之间通信的核心机制。Go 的哲学是"不要通过共享内存来通信，而应通过通信来共享内存"。Channel 就像一个类型安全的管道，让 goroutine 之间可以安全地传递数据，无需手动加锁。

![Channel数据流](https://img.zhaojq.top/20260729163058662.png "Channel数据流")

## 创建 channel

使用 `make` 函数创建 channel，`chan` 关键字声明 channel 类型。

```go
package main

import "fmt"

func main() {
	// 创建无缓冲 channel
	ch1 := make(chan int)

	// 创建有缓冲 channel（容量为 10）
	ch2 := make(chan string, 10)

	fmt.Printf("ch1 类型: %T, 容量: %d\n", ch1, cap(ch1))
	fmt.Printf("ch2 类型: %T, 容量: %d\n", ch2, cap(ch2))
}
```

`make(chan T)` 创建无缓冲 channel，`make(chan T, capacity)` 创建有缓冲 channel。容量为 0 或省略时为无缓冲。

## 发送和接收

`<-` 操作符用于发送和接收数据。方向取决于它在 `chan` 的哪一侧。

```go
package main

import (
	"fmt"
	"time"
)

func sender(ch chan int) {
	for i := 1; i <= 5; i++ {
		ch <- i // 发送到 channel
		fmt.Printf("发送: %d\n", i)
		time.Sleep(50 * time.Millisecond)
	}
	close(ch) // 发送完毕后关闭
}

func receiver(ch chan int, done chan bool) {
	for {
		val, ok := <-ch // 从 channel 接收
		if !ok {
			fmt.Println("channel 已关闭")
			done <- true
			return
		}
		fmt.Printf("接收: %d\n", val)
	}
}

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

	go sender(ch)
	go receiver(ch, done)

	<-done // 等待 receiver 完成
	fmt.Println("程序结束")
}
```

`ch <- value` 发送数据，`value := <-ch` 接收数据。从已关闭的 channel 接收会返回零值和 `ok = false`。

## 无缓冲 channel（同步通信）

无缓冲 channel 的发送和接收必须同时就绪，否则先执行的一方会阻塞。这实现了 goroutine 之间的同步。

```go
package main

import (
	"fmt"
	"time"
)

func ping(ch chan string) {
	msg := <-ch // 接收
	fmt.Println("ping 收到:", msg)

	ch <- "pong" // 回复
	fmt.Println("ping 已回复")
}

func main() {
	ch := make(chan string) // 无缓冲 channel

	go ping(ch)

	// 主 goroutine 先发送
	ch <- "hello" // 如果 ping 还没准备好接收，这里会阻塞
	fmt.Println("main 已发送")

	// 等待 ping 的回复
	reply := <-ch
	fmt.Println("main 收到:", reply)
}
```

无缓冲 channel 保证了发送方和接收方的"握手"——双方都到达时数据才会传递。这种特性常用于 goroutine 之间的信号同步。

## 有缓冲 channel（异步通信）

有缓冲 channel 允许在无人接收时暂存数据，发送方只在缓冲区满时才阻塞。

```go
package main

import "fmt"

func producer(ch chan int, count int) {
	for i := 0; i < count; i++ {
		ch <- i
		fmt.Printf("生产: %d (缓冲: %d/%d)\n", i, len(ch), cap(ch))
	}
	close(ch)
}

func main() {
	ch := make(chan int, 5) // 缓冲区容量为 5

	go producer(ch, 8)

	// 等待生产者完成一部分后开始消费
	for val := range ch {
		fmt.Printf("消费: %d (缓冲: %d/%d)\n", val, len(ch), cap(ch))
	}
	fmt.Println("所有数据处理完毕")
}
```

有缓冲 channel 的 `len(ch)` 返回当前缓冲的元素个数，`cap(ch)` 返回缓冲区容量。当缓冲区满时发送阻塞，当缓冲区空时接收阻塞。

## channel 方向

可以限制 channel 只能发送或只能接收，增强代码的类型安全性。

```go
package main

import "fmt"

// producer 只发送数据
func producer(ch chan<- int) {
	for i := 0; i < 5; i++ {
		ch <- i
	}
	close(ch)
}

// consumer 只接收数据
func consumer(ch <-chan int) {
	for val := range ch {
		fmt.Printf("收到: %d\n", val)
	}
}

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

	go producer(ch) // chan int 可以隐转换为 chan<- int
	consumer(ch)    // chan int 可以隐转换为 <-chan int

	fmt.Println("处理完毕")
}
```

`chan<- T` 表示只写 channel，`<-chan T` 表示只读 channel。将 `chan T` 传给参数时会自动转换方向限制。这种设计让函数签名清楚地表达了 channel 的使用意图。

## close() 函数

`close` 关闭 channel，关闭后不能再发送数据，但可以继续接收。

```go
package main

import "fmt"

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

	// 发送数据
	ch <- 1
	ch <- 2
	ch <- 3
	close(ch) // 关闭 channel

	// 关闭后仍可接收
	fmt.Println(<-ch) // 1
	fmt.Println(<-ch) // 2
	fmt.Println(<-ch) // 3

	// 缓冲区空后继续接收，返回零值
	val, ok := <-ch
	fmt.Printf("val=%d, ok=%v\n", val, ok) // val=0, ok=false

	// 关闭后发送会 panic
	// ch <- 4 // panic: send on closed channel

	// 检查 channel 是否关闭
	if !ok {
		fmt.Println("channel 已关闭且为空")
	}
}
```

关闭 channel 的规则：
- 只能由发送方关闭，不能由接收方关闭
- 关闭后不能发送，但可以接收剩余数据
- 重复关闭会 panic
- 向 nil channel 发送或接收会永久阻塞

## range 遍历 channel

`for range` 可以持续从 channel 接收数据，直到 channel 被关闭。

```go
package main

import "fmt"

func fibonacci(ch chan int, n int) {
	a, b := 0, 1
	for i := 0; i < n; i++ {
		ch <- a
		a, b = b, a+b
	}
	close(ch) // 必须关闭，否则 range 会永久阻塞
}

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

	go fibonacci(ch, 10)

	// range 会自动接收直到 channel 关闭
	fmt.Print("斐波那契数列: ")
	for val := range ch {
		fmt.Printf("%d ", val)
	}
	fmt.Println()
}
```

`for val := range ch` 等价于手动检查 `ok` 值的循环，代码更简洁。

## nil channel 的行为

未初始化的 channel（值为 nil）在读写时都会永久阻塞。

```go
package main

import (
	"fmt"
	"time"
)

func main() {
	var ch chan int // nil channel

	// 向 nil channel 发送会永久阻塞
	go func() {
		ch <- 1
		fmt.Println("这行不会执行")
	}()

	// 从 nil channel 接收也会永久阻塞
	go func() {
		<-ch
		fmt.Println("这行也不会执行")
	}()

	time.Sleep(100 * time.Millisecond)
	fmt.Println("主 goroutine 继续执行")

	// nil channel 的常见用途：在 select 中禁用某个 case
	readCh := make(chan int, 1)
	readCh <- 42
	var writeCh chan int // nil，暂时禁用写入

	select {
	case val := <-readCh:
		fmt.Println("读取:", val)
	case writeCh <- 1:
		fmt.Println("这行不会执行（writeCh 是 nil）")
	}
}
```

nil channel 在 `select` 语句中很有用——将某个 channel 设为 nil 可以临时禁用对应的 case 分支。

Channel 的数据流可以这样理解：发送方通过 `<-` 将数据放入管道，接收方通过 `<-` 从管道取出数据。无缓冲 channel 要求发送和接收同步就绪，有缓冲 channel 允许数据暂存。多个 goroutine 可以安全地向同一个 channel 发送或接收数据，channel 内部通过互斥锁保证并发安全。

## 实用模式

下面展示几个 channel 的常见使用模式。

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

// 模式 1：信号通知
func signalPattern() {
	done := make(chan struct{})

	go func() {
		fmt.Println("后台任务执行中...")
		time.Sleep(100 * time.Millisecond)
		fmt.Println("后台任务完成")
		close(done) // 发送完成信号
	}()

	<-done // 等待信号
	fmt.Println("主程序继续")
}

// 模式 2：数据管道
func pipelinePattern() {
	// 生成数据
	nums := make(chan int)
	go func() {
		for i := 1; i <= 5; i++ {
			nums <- i
		}
		close(nums)
	}()

	// 平方处理
	squares := make(chan int)
	go func() {
		for n := range nums {
			squares <- n * n
		}
		close(squares)
	}()

	// 收集结果
	for s := range squares {
		fmt.Printf("%d ", s)
	}
	fmt.Println()
}

// 模式 3：扇出 - 多个 goroutine 从同一 channel 读取
func fanOutPattern() {
	jobs := make(chan int, 10)
	var wg sync.WaitGroup

	// 启动 3 个 worker
	for w := 1; w <= 3; w++ {
		wg.Add(1)
		go func(workerID int) {
			defer wg.Done()
			for job := range jobs {
				fmt.Printf("Worker %d 处理任务 %d\n", workerID, job)
				time.Sleep(50 * time.Millisecond)
			}
		}(w)
	}

	// 发送 6 个任务
	for j := 1; j <= 6; j++ {
		jobs <- j
	}
	close(jobs)

	wg.Wait()
	fmt.Println("所有任务处理完毕")
}

func main() {
	fmt.Println("=== 信号通知模式 ===")
	signalPattern()

	fmt.Println("\n=== 管道模式 ===")
	pipelinePattern()

	fmt.Println("\n=== 扇出模式 ===")
	fanOutPattern()
}
```

## 总结

Channel 是 Go 并发通信的核心。无缓冲 channel 实现同步通信，有缓冲 channel 允许异步操作。`<-` 操作符用于发送和接收，`close()` 关闭 channel。通过 channel 方向限制（`chan<-`、`<-chan`）可以增强类型安全。`for range` 可以简洁地遍历 channel。nil channel 在 `select` 中可用于禁用分支。掌握 channel 的各种模式（信号通知、管道、扇出/扇入）是编写高效并发程序的关键。

