<返回目录     Powered by claud/xia兄

第9课: Channels - 通道

什么是Channel?

Channel(通道)是Go语言中goroutine之间通信的管道。通过channel,goroutine可以安全地发送和接收数据,实现同步和数据共享。

Channel的核心理念:

"不要通过共享内存来通信,而应该通过通信来共享内存"

Channel提供了一种类型安全的通信机制,避免了传统并发编程中的竞态条件和锁的复杂性。

创建Channel

基本语法

package main

import "fmt"

func main() {
    // 创建一个int类型的channel
    ch := make(chan int)

    // 创建一个string类型的channel
    messages := make(chan string)

    // 创建一个带缓冲的channel
    buffered := make(chan int, 5)

    fmt.Println("Channel创建成功")
}

Channel类型

类型 语法 说明
无缓冲channel make(chan T) 发送和接收必须同时准备好,否则阻塞
有缓冲channel make(chan T, n) 缓冲区未满时发送不阻塞,缓冲区非空时接收不阻塞
只读channel <-chan T 只能接收数据
只写channel chan<- T 只能发送数据

Channel操作

发送和接收

package main

import "fmt"

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

    // 在goroutine中发送数据
    go func() {
        ch <- "Hello, Channel!" // 发送数据到channel
    }()

    // 从channel接收数据
    msg := <-ch // 接收数据
    fmt.Println(msg)
}

关闭Channel

package main

import "fmt"

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

    // 发送数据
    ch <- 1
    ch <- 2
    ch <- 3

    // 关闭channel
    close(ch)

    // 从已关闭的channel接收数据
    for num := range ch {
        fmt.Println(num)
    }

    // 检查channel是否关闭
    val, ok := <-ch
    if !ok {
        fmt.Println("Channel已关闭")
    }
}
关闭Channel的注意事项:

无缓冲Channel

无缓冲channel也称为同步channel,发送和接收操作必须同时准备好。

package main

import (
    "fmt"
    "time"
)

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

    go func() {
        time.Sleep(time.Second)
        ch <- "数据已准备好"
    }()

    fmt.Println("等待数据...")
    msg := <-ch // 阻塞,直到接收到数据
    fmt.Println(msg)
}

有缓冲Channel

有缓冲channel允许在缓冲区未满时异步发送数据。

package main

import "fmt"

func main() {
    // 创建容量为3的缓冲channel
    ch := make(chan int, 3)

    // 发送数据(不会阻塞)
    ch <- 1
    ch <- 2
    ch <- 3

    // 接收数据
    fmt.Println(<-ch) // 1
    fmt.Println(<-ch) // 2
    fmt.Println(<-ch) // 3
}

缓冲区状态

package main

import "fmt"

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

    ch <- 1
    ch <- 2
    ch <- 3

    // 获取channel长度(当前元素数量)
    fmt.Println("长度:", len(ch)) // 3

    // 获取channel容量
    fmt.Println("容量:", cap(ch)) // 5
}

Select语句

select语句用于处理多个channel操作,类似于switch语句。

基本用法

package main

import (
    "fmt"
    "time"
)

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

    go func() {
        time.Sleep(time.Second)
        ch1 <- "来自ch1"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "来自ch2"
    }()

    // select会阻塞,直到某个case可以执行
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

带default的Select

package main

import "fmt"

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

    select {
    case val := <-ch:
        fmt.Println("接收到:", val)
    default:
        fmt.Println("没有数据可接收") // 立即执行
    }

    // 非阻塞发送
    select {
    case ch <- 1:
        fmt.Println("发送成功")
    default:
        fmt.Println("无法发送") // 立即执行
    }
}

超时控制

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(time.Second):
        fmt.Println("超时!")
    }
}

Channel方向

可以指定channel的方向,限制只能发送或只能接收。

package main

import "fmt"

// 只能发送的channel
func send(ch chan<- int) {
    ch <- 42
}

// 只能接收的channel
func receive(ch <-chan int) {
    val := <-ch
    fmt.Println("接收到:", val)
}

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

    go send(ch)
    receive(ch)
}

Channel模式

1. 生产者-消费者模式

package main

import (
    "fmt"
    "time"
)

func producer(ch chan<- int) {
    for i := 1; i <= 5; i++ {
        fmt.Printf("生产: %d\n", i)
        ch <- i
        time.Sleep(time.Millisecond * 500)
    }
    close(ch)
}

func consumer(ch <-chan int) {
    for num := range ch {
        fmt.Printf("消费: %d\n", num)
        time.Sleep(time.Second)
    }
}

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

2. Pipeline模式

package main

import "fmt"

func generator(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() {
    // 构建pipeline
    nums := generator(1, 2, 3, 4, 5)
    squares := square(nums)

    // 消费结果
    for result := range squares {
        fmt.Println(result)
    }
}

实战示例

示例1:并发爬虫

package main

import (
    "fmt"
    "time"
)

type Result struct {
    URL  string
    Data string
}

func crawl(url string, results chan<- Result) {
    time.Sleep(time.Second) // 模拟网络请求
    results <- Result{
        URL:  url,
        Data: fmt.Sprintf("来自 %s 的数据", url),
    }
}

func main() {
    urls := []string{
        "https://example.com/page1",
        "https://example.com/page2",
        "https://example.com/page3",
    }

    results := make(chan Result, len(urls))

    for _, url := range urls {
        go crawl(url, results)
    }

    for i := 0; i < len(urls); i++ {
        result := <-results
        fmt.Printf("URL: %s, Data: %s\n", result.URL, result.Data)
    }
}

最佳实践

常见错误:

实践练习

  1. 实现一个简单的消息队列
  2. 使用channel实现信号量(限制并发数)
  3. 编写一个并发的任务调度器
  4. 实现一个带超时的HTTP请求
  5. 使用pipeline模式处理数据流
  6. 实现一个发布-订阅系统
  7. 编写一个并发安全的缓存
  8. 实现一个工作池,支持动态调整worker数量