第27章 通道 Channel

第27章 通道 Channel

通道是Go语言最有特色的发明!如果把Goroutine比作"分身术",那Channel就是"电话线"——让分身之间可以互相通信、数据共享。

想象电话接线员:Goroutine们想通信,得通过Channel来传递消息。Channel保证了:

  1. 通信的同步性(发送和接收必须配对)
  2. 消息的安全性(一次只被一个接收者消费)

27.1 通道类型

27.1.1 声明与创建

通道是引用类型,用 make 创建:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
    // 声明通道(零值是nil)
    var ch1 chan int
    var ch2 chan string

    fmt.Printf("ch1: %v (nil通道)\n", ch1) // ch1: <nil> (nil通道)
    fmt.Printf("ch2: %v (nil通道)\n", ch2) // ch2: <nil> (nil通道)

    // 用make创建通道
    intCh := make(chan int)      // 无缓冲通道
    stringCh := make(chan string) // 无缓冲通道

    fmt.Printf("intCh类型: %T\n", intCh)     // intCh类型: chan int
    fmt.Printf("stringCh类型: %T\n", stringCh) // stringCh类型: chan string
}

通道类型说明:

  • chan T - 发送和接收T类型数据
  • chan<- T - 只能发送T类型数据
  • <-chan T - 只能接收T类型数据

27.1.2 缓冲通道

无缓冲通道:发送和接收必须配对,否则阻塞。 缓冲通道:有容量,发送直到满才阻塞,接收直到空才阻塞。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
    // 创建缓冲通道,容量为3
    buffered := make(chan int, 3)

    // 查看通道状态
    fmt.Printf("初始状态: len=%d, cap=%d\n", len(buffered), cap(buffered)) // len=0, cap=3

    // 发送3个数据
    buffered <- 1
    buffered <- 2
    buffered <- 3

    fmt.Printf("发送3个后: len=%d, cap=%d\n", len(buffered), cap(buffered)) // len=3, cap=3

    // 接收一个数据
    v1 := <-buffered
    fmt.Printf("取出%d,剩余: len=%d\n", v1, len(buffered)) // 取出1,剩余: len=2
}

缓冲 vs 无缓冲:

特性无缓冲 make(chan T)缓冲 make(chan T, n)
容量0n
发送阻塞需等接收者需等缓冲区满
接收阻塞需等发送者需等缓冲区空

27.2 通道操作

27.2.1 发送操作

<- 箭头指向数据流向:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
    // 创建容量为2的通道
    ch := make(chan int, 2)

    // 发送数据
    ch <- 1
    fmt.Println("发送 1 成功") // 发送 1 成功

    ch <- 2
    fmt.Println("发送 2 成功") // 发送 2 成功

    fmt.Printf("通道内数据量: %d\n", len(ch)) // 通道内数据量: 2
}

27.2.2 接收操作

接收数据有两种方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3

    // 方式1:只接收值
    v1 := <-ch
    fmt.Printf("v1 = %d\n", v1) // v1 = 1

    // 方式2:接收值和状态(comma-ok)
    v2, ok := <-ch
    fmt.Printf("v2 = %d, ok = %t\n", v2, ok) // v2 = 2, ok = true

    v3 := <-ch
    fmt.Printf("v3 = %d\n", v3) // v3 = 3

    // 通道已空,再接收
    v4, ok := <-ch
    fmt.Printf("v4 = %d, ok = %t (通道已空)\n", v4, ok) // v4 = 0, ok = false
}

27.2.3 关闭通道

发送者关闭通道,接收者通过 ok 判断通道是否已关闭:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)  // 关闭通道

    // 关闭后仍可接收数据,直到通道空
    v1, ok := <-ch
    fmt.Printf("v1=%d, ok=%t\n", v1, ok) // v1=1, ok=true
    v2, ok := <-ch
    fmt.Printf("v2=%d, ok=%t\n", v2, ok) // v2=2, ok=true
    v3, ok := <-ch
    fmt.Printf("v3=%d, ok=%t\n", v3, ok) // v3=3, ok=true

    // 通道已空
    v4, ok := <-ch
    fmt.Printf("v4=%d, ok=%t (通道已关闭且为空)\n", v4, ok) // v4=0, ok=false
}

注意:

  • 关闭已关闭的通道会panic
  • 向已关闭的通道发送会panic
  • 通道关闭后,接收方会收到零值

27.3 select 语句

select 类似于switch,但用于通道操作——等待多个通道,直到其中一个准备好。

27.3.1 基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)

    ch1 <- 10  // ch1准备好了

    // select随机选择一个就绪的case执行
    select {
    case v1 := <-ch1:
        fmt.Printf("从ch1收到: %d\n", v1) // 从ch1收到: 10
    case v2 := <-ch2:
        fmt.Printf("从ch2收到: %d\n", v2)
    }
}

27.3.2 超时处理

time.After 实现超时:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
    "fmt"
    "time"
)

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

    // 模拟:1秒后发送数据
    go func() {
        time.Sleep(1 * time.Second)
        ch <- 42
    }()

    // 等待500ms,如果没收到就超时
    select {
    case v := <-ch:
        fmt.Printf("收到: %d\n", v) // 收到: 42
    case <-time.After(500 * time.Millisecond):
        fmt.Println("超时!等了500ms还没收到") // 超时!等了500ms还没收到
    }
}

27.3.3 default 分支

当没有任何通道准备好时,执行default(如果有):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
    ch := make(chan int, 1)
    ch <- 1

    // ch1有数据,执行这个case
    select {
    case v := <-ch:
        fmt.Printf("收到: %d\n", v) // 收到: 1
    default:
        fmt.Println("通道未就绪")
    }

    // 空通道,走default
    emptyCh := make(chan int)

    select {
    case v := <-emptyCh:
        fmt.Printf("收到: %d\n", v)
    default:
        fmt.Println("通道为空,执行default") // 通道为空,执行default
    }
}

27.4 通道模式

27.4.1 信号模式

用空结构体 struct{} 作为信号,不需要传递数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
    "fmt"
    "time"
)

func worker(done chan struct{}) {
    fmt.Println("Worker: 开始工作...")
    for i := 0; i < 3; i++ {
        fmt.Printf("  工作进度 %d/3\n", i+1)
        time.Sleep(50 * time.Millisecond)
    }
    fmt.Println("Worker: 完工!")
    done <- struct{}{}  // 发送完成信号
}

func main() {
    done := make(chan struct{})  // 空结构体通道,不传递数据

    go worker(done)

    fmt.Println("主函数: 等待Worker完成...")
    <-done  // 等待信号

    fmt.Println("主函数: 收到完成信号!")

    // 主函数: 等待Worker完成...
    // Worker: 开始工作...
    //   工作进度 1/3
    //   工作进度 2/3
    //   工作进度 3/3
    // Worker: 完工!
    // 主函数: 收到完成信号!
}

27.4.2 管道模式

管道:数据从一系列阶段流过,每个阶段可能 transforming 数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import "fmt"

func producer(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() {
    // 创建管道:producer -> square -> 输出
    pipeline := producer(1, 2, 3, 4, 5)  // 输出: 1, 2, 3, 4, 5
    squared := square(pipeline)            // 平方: 1, 4, 9, 16, 25

    fmt.Print("平方结果: ")
    first := true
    for v := range squared {  // 遍历,直到通道关闭
        if !first {
            fmt.Print(", ")
        }
        fmt.Print(v)
        first = false
    }
    fmt.Println()

    // 平方结果: 1, 4, 9, 16, 25
}

管道图解:

producer(1,2,3,4,5) --> [1] --> [2] --> [3] --> [4] --> [5] --> close
                              |
                              v
                        square: n*n
                              |
                              v
                         [1] --> [4] --> [9] --> [16] --> [25] --> close

27.5 通道进阶

27.5.1 单向通道

通道可以限制方向:只能发送或只能接收:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

type Sender chan<- int      // 只能发送
type Receiver <-chan int    // 只能接收

// producer 只发送,不接收
func producer(out chan<- int) {
    out <- 1
    out <- 2
    out <- 3
    close(out)
}

// consumer 只接收,不发送
func consumer(in <-chan int) {
    for v := range in {
        fmt.Printf("收到: %d\n", v)
    }
}

func main() {
    ch := make(chan int)
    go producer(ch)
    consumer(ch)

    // 收到: 1
    // 收到: 2
    // 收到: 3
}

27.5.2 遍历通道

for range 自动等待通道关闭:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

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

    // 发送者
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
        }
        close(ch)  // 重要:关闭通道
    }()

    // range自动等待通道关闭
    fmt.Print("收到: ")
    for v := range ch {
        fmt.Printf("%d ", v)
    }
    fmt.Println()

    // 收到: 1 2 3 4 5
}

本章小结

本章我们学习了通道:

通道基础:

  • make(chan T) 创建无缓冲通道
  • make(chan T, n) 创建缓冲通道
  • <- 发送和接收操作

select语句:

  • 多通道选择:随机选择已就绪的case
  • 超时处理:case <-time.After()
  • default分支:非阻塞

通道模式:

  • 信号模式:用 chan struct{} 表示完成
  • 管道模式:一系列处理阶段串联

最佳实践:

  • 发送者关闭通道(接收者不要关闭)
  • 使用 range 遍历通道直到关闭
  • comma-ok 判断通道状态
最后修改 March 20, 2026: 新增Go基础部分 (8cce995)