在 Go 语言中,Goroutines
(协程)和 Channels
(通道)是并发编程的核心组件。它们共同协作,简化了并发任务的管理和数据同步。以下通过详细示例说明它们的用法和常见模式。
1. Goroutines(协程)
Goroutine 是轻量级线程,由 Go 运行时调度,启动成本极低(通常仅几 KB 内存)。
基本用法
通过 go
关键字启动一个 Goroutine:
go
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 3; i++ {
fmt.Println("Number:", i)
time.Sleep(100 * time.Millisecond)
}
}
func printLetters() {
for c := 'a'; c <= 'c'; c++ {
fmt.Println("Letter:", string(c))
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go printNumbers() // 启动 Goroutine
go printLetters()
// 主 Goroutine 等待其他协程执行
time.Sleep(1 * time.Second)
fmt.Println("Main Goroutine 结束")
}
输出(顺序可能不同):
Letter: a
Number: 1
Number: 2
Letter: b
Number: 3
Letter: c
Main Goroutine 结束
2. Channels(通道)
Channel 是类型化的管道,用于 Goroutines 之间的通信和同步。
基本用法
go
func worker(done chan bool) {
fmt.Println("Worker 开始工作...")
time.Sleep(1 * time.Second)
fmt.Println("Worker 完成工作")
done <- true // 发送完成信号
}
func main() {
done := make(chan bool) // 创建布尔型通道
go worker(done)
<-done // 阻塞,直到接收到数据
fmt.Println("主程序收到完成信号")
}
输出:
Worker 开始工作...
Worker 完成工作
主程序收到完成信号
3. 缓冲通道(Buffered Channels)
允许在没有接收者时缓存一定数量的数据。
go
func main() {
messages := make(chan string, 2) // 缓冲容量为 2
messages <- "消息1" // 不阻塞(缓存未满)
messages <- "消息2"
fmt.Println(<-messages) // 输出: 消息1
fmt.Println(<-messages) // 输出: 消息2
}
4. 通道方向(Channel Direction)
限制通道在函数中的使用方式(只读或只写)。
go
// 只写通道参数
func sendData(ch chan<- string, msg string) {
ch <- msg
}
// 只读通道参数
func receiveData(ch <-chan string) {
fmt.Println("收到消息:", <-ch)
}
func main() {
ch := make(chan string)
go sendData(ch, "Hello")
receiveData(ch)
}
输出:
收到消息: Hello
5. Select 语句
监听多个通道操作,处理第一个就绪的通道。
go
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "来自 ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "来自 ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}
输出:
来自 ch1
来自 ch2
6. 关闭通道与遍历通道
通过 close
关闭通道,通过 range
遍历通道数据。
go
func produceNumbers(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // 关闭通道
}
func main() {
ch := make(chan int)
go produceNumbers(ch)
// 循环读取直到通道关闭
for num := range ch {
fmt.Println("收到数字:", num)
}
}
输出:
收到数字: 0
收到数字: 1
收到数字: 2
收到数字: 3
收到数字: 4
7. 超时与错误处理
结合 select
和 time.After
实现超时控制。
go
func main() {
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second)
ch <- "数据"
}()
select {
case res := <-ch:
fmt.Println("收到数据:", res)
case <-time.After(2 * time.Second):
fmt.Println("超时!")
}
}
输出:
超时!
8. Worker Pool(工作池)
使用缓冲通道和多个 Goroutines 构建任务处理池。
go
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d 开始处理任务 %d\n", id, job)
time.Sleep(1 * time.Second)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
// 启动 3 个 Worker
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 发送 5 个任务
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// 收集结果
for a := 1; a <= 5; a++ {
<-results
}
}
输出:
Worker 1 开始处理任务 1
Worker 2 开始处理任务 2
Worker 3 开始处理任务 3
Worker 1 开始处理任务 4
Worker 2 开始处理任务 5
总结
- Goroutines :
- 通过
go
关键字启动。 - 轻量级,适合高并发场景。
- 通过
- Channels :
- 同步通信:无缓冲通道需发送和接收同时就绪。
- 异步通信:缓冲通道允许暂存数据。
- 使用
close
关闭通道,range
遍历通道数据。
- 高级模式 :
select
多路监听。- 超时控制、工作池、只读/只写通道。
- 注意事项 :
- 避免死锁(如未关闭的通道或未接收的数据)。
- 使用
sync.WaitGroup
等待多个 Goroutines 完成。