ZetCode

Golang chan 关键字

最后修改于 2025 年 5 月 7 日

本教程将解释如何在 Go 中使用 chan 关键字。我们将通过并发编程的实际示例来介绍通道基础知识。

chan 关键字用于声明一个通道类型,用于 goroutine 之间的通信。通道是类型化的管道,允许你发送和接收值。

在 Go 中,chan 是安全并发编程的基础。通道通过设计来同步 goroutine 并防止竞态条件。

基本通道创建

chan 最简单的用法是创建一个无缓冲通道。本示例演示了基本的通道声明和用法。

basic_channel.go
package main

import "fmt"

func main() {
    // Create an unbuffered channel of integers
    ch := make(chan int)
    
    go func() {
        // Send value through channel
        ch <- 42
    }()
    
    // Receive value from channel
    val := <-ch
    fmt.Println("Received:", val)
}

通道 ch 在 goroutine 之间传输一个整数。主 goroutine 会阻塞,直到接收到该值。

缓冲通道

缓冲通道允许在不阻塞的情况下发送多个值。本示例展示了如何创建和使用它们。

buffered_channel.go
package main

import "fmt"

func main() {
    // Create a buffered channel with capacity 3
    ch := make(chan string, 3)
    
    // Send values without blocking
    ch <- "first"
    ch <- "second"
    ch <- "third"
    
    // Receive values
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

通道在阻塞之前可以容纳三个字符串。值按 FIFO(先进先出)顺序接收。缓冲通道可以解耦发送者和接收者。

通道方向

通道参数可以限制为仅发送或仅接收。本示例演示了定向通道。

channel_directions.go
package main

import "fmt"

// sendOnly can only send to channel
func sendOnly(ch chan<- int, val int) {
    ch <- val
}

// receiveOnly can only receive from channel
func receiveOnly(ch <-chan int) int {
    return <-ch
}

func main() {
    ch := make(chan int)
    
    go sendOnly(ch, 99)
    val := receiveOnly(ch)
    
    fmt.Println("Received:", val)
}

定向通道使函数契约更清晰。chan<- 表示仅发送,而 <-chan 表示仅接收。

通道同步

通道可以同步 goroutine。本示例展示了一种常见的 worker 模式。

channel_sync.go
package main

import (
    "fmt"
    "time"
)

func worker(done chan bool) {
    fmt.Print("Working...")
    time.Sleep(time.Second)
    fmt.Println("done")
    
    done <- true
}

func main() {
    done := make(chan bool)
    
    go worker(done)
    
    // Block until worker finishes
    <-done
    fmt.Println("Worker completed")
}

主 goroutine 等待 worker 发送完成信号。通道提供简单的同步,无需锁。

带通道的 Select 语句

select 语句处理多个通道操作。本示例演示了非阻塞通道操作。

channel_select.go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "one"
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "two"
    }()
    
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received", msg2)
        }
    }
}

select 语句会等待多个通道操作。它会执行第一个就绪的 case。这对于超时和多路复用很有用。

通道超时

通道可以使用 select 实现超时。本示例展示了如何防止无限阻塞。

channel_timeout.go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string)
    
    go func() {
        time.Sleep(3 * time.Second)
        ch <- "result"
    }()
    
    select {
    case res := <-ch:
        fmt.Println(res)
    case <-time.After(2 * time.Second):
        fmt.Println("timeout")
    }
}

time.After 创建一个在指定时间后发送值的通道。Select 语句会在结果和超时之间进行选择。

关闭通道

关闭通道表示不再发送值。本示例演示了正确的通道关闭方法。

channel_close.go
package main

import "fmt"

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

func main() {
    ch := make(chan int)
    
    go producer(ch)
    
    for {
        val, ok := <-ch
        if !ok {
            fmt.Println("Channel closed")
            break
        }
        fmt.Println("Received:", val)
    }
}

生产者在发送完所有值后关闭通道。接收者可以使用第二个返回值检测通道是否已关闭。

来源

Go 语言规范

本教程通过通道在并发编程中的实际用法示例,讲解了 Go 中的 chan 关键字。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Golang 教程