chan 是 Go 语言并发编程中最核心的概念之一,它是 Channel(通道) 类型的关键字缩写。


Channel 的设计理念源于通信顺序进程(CSP, Communicating Sequential Processes),它提供了一种安全、同步的方式,让不同的 Goroutine(并发执行的“工人”)之间可以进行通信和数据交换。

1.什么是 chan (通道)?

Channel 可以被理解为一个管道或队列,它具有以下核心特性:

类型安全: Channel 只能传输它在创建时指定的特定类型的数据。
例如:chan int 只能传输 int 整数。
同步机制: Channel 默认会阻塞发送和接收操作,直到另一端准备好。
并发安全: Go 运行时保证了对 Channel 的发送和接收操作是线程安全的,无需额外的锁(sync.Mutex)。

2.chan 怎么使用?

使用 Channel 主要分为三个步骤:创建、发送、接收。

2.1 创建 Channel

使用 make 函数创建 Channel。

//无缓冲通道 (Unbuffered)
ch := make(chan Type)
//容量为 0。发送和接收操作必须同时准备好,否则先执行的操作会一直阻塞,直到另一个操作发生。用于严格的同步。

ch := make(chan Type, N)
//有缓冲通道 (Buffered)
//容量为 N。通道可以存储 N个元素。只有当通道满了(发送)或空了(接收)时,操作才会阻塞。用于解耦和提高吞吐量。

dataCh := make(chan string)       // 无缓冲,用于同步信号
taskCh := make(chan int, 10)      // 有缓冲,容量为 10,用于传输任务
类型语法目的
切片make([]Type, length, capacity)分配底层数组,设置切片的长度和容量。
映射make(map[KeyType]ValueType, capacity)分配和初始化哈希表结构。
通道make(chan Type, capacity)创建通道并设置其缓冲大小。

2.2 发送数据

使用箭头操作符 <- 将数据发送到 Channel。

ch <- value
//示例:
taskCh <- 5 // 将整数 5 发送到 taskCh~~~

2.3 接收数据

使用箭头操作符 <- 从 Channel 接收数据。

value := <-ch             // 接收数据,并赋值给 value
value, ok := <-ch         // 接收数据,并检查通道是否已关闭
  • 示例
// 接收并赋值
taskId := <-taskCh

// 接收并检查状态(常用于循环接收)
id, open := <-taskCh
if !open {
    // 通道已被关闭且数据已取完
}

3.使用的时候要注意什么?

使用 Channel 必须非常小心,错误的用法可能导致程序死锁、数据丢失或性能问题。

3.1避免死锁 (Deadlock)

死锁是使用 Channel 时最常见的问题。 如果一个 Goroutine 试图发送数据到一个无缓冲通道,但没有另一个 Goroutine 准备好接收,该 Goroutine 就会永远阻塞,Go 运行时会检测到这种情况并报告致命错误:fatal error: all goroutines are asleep - deadlock!

规则: 无缓冲通道 总是需要一个发送方和一个接收方同时在场。
避免: 绝对不要在同一个 Goroutine 中对无缓冲通道进行发送和接收操作。

3.2关闭 Channel

Channel 可以通过 close(ch) 函数关闭,表示不会再有数据发送。

只能发送方关闭: 应该由发送方来关闭 Channel,而不是接收方。
重复关闭 panic: 重复关闭一个 Channel 会导致 panic。
关闭后的接收: 关闭后,接收方仍可以接收到通道中剩余的数据。当数据取完后,接收操作会立即返回该类型的零值,ok 状态为 false。

3.3缓冲区的误解

有缓冲通道 不是无限队列。一旦缓冲区满了,发送操作仍然会阻塞。
合理设置缓冲区大小是提高性能的关键,但过大或过小都可能带来问题。

3.4使用 select 语句处理多 Channel 和超时

当 Goroutine 需要同时监听多个 Channel 的读写操作时,必须使用 select 语句。

select 语句可以实现:
监听多个输入 Channel。
监听多个输出 Channel。
通过 default 块实现非阻塞操作。
结合 time.After 实现超时(Timeout)机制。

具体实现

select {
case msg := <-ch1:
    // 接收到 ch1 的数据
case ch2 <- "hi":
    // 成功发送数据到 ch2
case <-time.After(5 * time.Second):
    // 超时处理
default:
    // 如果没有任何 Channel 准备好,立即执行 default (非阻塞)
}