Channel 通道

Channel 是 Go 语言中 goroutine 之间通信的管道。本章将详细介绍 Channel 的使用方法。

什么是 Channel?

Channel 是一种类型安全的管道,用于在 goroutine 之间传递数据。

Channel 示意:

goroutine A  ──→  ┌─────────┐  ──→  goroutine B
                  │ Channel │
                  └─────────┘

Go 的并发哲学:不要通过共享内存来通信,而要通过通信来共享内存。

创建 Channel

使用 make

package main

import "fmt"

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

    fmt.Printf("类型: %T, 值: %v\n", ch, ch)
}

缓冲 vs 无缓冲

ch := make(chan int)        // 无缓冲
ch := make(chan int, 10)    // 缓冲,容量 10

基本操作

发送和接收

package main

import "fmt"

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

    go func() {
        ch <- "Hello"
    }()

    msg := <-ch
    fmt.Println(msg)
}

发送和接收的语法

ch <- value    // 发送
value := <-ch  // 接收
value, ok := <-ch  // 接收并检查是否关闭

无缓冲 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() {
    ch := make(chan int, 3)

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

    fmt.Println("发送了 3 个值")

    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

关闭 Channel

关闭语法

close(ch)

检查是否关闭

package main

import "fmt"

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

    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    for {
        value, ok := <-ch
        if !ok {
            fmt.Println("通道已关闭")
            break
        }
        fmt.Println(value)
    }
}

range 遍历

package main

import "fmt"

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

    go func() {
        ch <- 1
        ch <- 2
        ch <- 3
        close(ch)
    }()

    for value := range ch {
        fmt.Println(value)
    }
}

单向 Channel

只发送

func send(ch chan<- int) {
    ch <- 1
}

只接收

func receive(ch <-chan int) {
    value := <-ch
    fmt.Println(value)
}

示例

package main

import "fmt"

func producer(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
}

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

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

    go producer(ch)
    consumer(ch)
}

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 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

超时处理

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("超时")
    }
}

非阻塞操作

package main

import "fmt"

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

    select {
    case ch <- 1:
        fmt.Println("发送成功")
    default:
        fmt.Println("发送失败")
    }

    select {
    case value := <-ch:
        fmt.Printf("接收: %d\n", value)
    default:
        fmt.Println("没有数据")
    }
}

实际案例

工作池

package main

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

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("worker %d 处理任务 %d\n", id, job)
        time.Sleep(500 * time.Millisecond)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)

    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, jobs, results, &wg)
    }

    for i := 1; i <= 5; i++ {
        jobs <- i
    }
    close(jobs)

    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Printf("结果: %d\n", result)
    }
}

生产者消费者

package main

import (
    "fmt"
    "time"
)

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

func consumer(ch <-chan int, done chan<- bool) {
    for value := range ch {
        fmt.Printf("消费: %d\n", value)
        time.Sleep(200 * time.Millisecond)
    }
    done <- true
}

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

    go producer(ch)
    go consumer(ch, done)

    <-done
    fmt.Println("完成")
}

总结

本章学习了 Go 语言的 Channel:

知识点说明
创建make(chan T)make(chan T, n)
发送ch <- value
接收value := <-ch
关闭close(ch)
遍历for v := range ch
select多 channel 处理