通过之前的文章,想必大家都已了解了Channel 是 Go 语言并发编程的核心工具,通过合理设计 Channel 的结构和使用方式,可以高效解决多线程协作中的通信、同步和资源管理问题。本文主要讲述 Go 语言中 Channel 在实际项目中的典型应用 示例,涵盖生产者-消费者、任务队列、事件通知、限流等场景:
1. 生产者-消费者模式
场景:多个生产者生成数据,多个消费者处理数据,通过 Channel 实现解耦和同步。
package main
import (
"fmt"
"sync"
"time"
)
func producer(id int, ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Printf("Producer %d produced: %d\n", id, i)
ch <- i // 发送数据到 Channel
time.Sleep(time.Millisecond * 100)
}
}
func consumer(id int, ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for num := range ch { // 从 Channel 读取数据
fmt.Printf("Consumer %d consumed: %d\n", id, num)
time.Sleep(time.Millisecond * 300) // 模拟处理时间
}
}
func main() {
ch := make(chan int, 10) // 带缓冲的 Channel
var wg sync.WaitGroup
// 启动 2 个生产者
for i := 1; i <= 2; i++ {
wg.Add(1)
go producer(i, ch, &wg)
}
// 启动 3 个消费者
for i := 1; i <= 3; i++ {
wg.Add(1)
go consumer(i, ch, &wg)
}
wg.Wait()
close(ch) // 关闭 Channel,避免消费者阻塞
}
关键点:
- Channel 缓冲区(
make(chan int, 10))解耦生产者和消费者的速度差异。 sync.WaitGroup确保所有 goroutine 完成后再关闭 Channel。range ch会自动停止读取,当 Channel 被关闭且无数据时。
2. 任务队列与协程池
场景:限制并发数量,避免资源耗尽(如数据库连接、HTTP 请求)。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, taskChan <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for task := range taskChan {
fmt.Printf("Worker %d processing task %d\n", id, task)
time.Sleep(time.Second) // 模拟任务处理时间
}
}
func main() {
taskChan := make(chan int, 100) // 任务队列
var wg sync.WaitGroup
// 创建 3 个协程池中的工作 goroutine
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, taskChan, &wg)
}
// 提交 10 个任务
for i := 1; i <= 10; i++ {
taskChan <- i
}
close(taskChan) // 关闭任务队列,通知所有 worker 退出
wg.Wait()
}
关键点:
- Channel 作为任务队列,协程池大小为 3,限制并发数量。
close(taskChan)广播通知所有 worker 停止处理任务。
3. 事件通知与优雅关闭
场景:通过关闭 Channel 通知所有监听者退出,实现服务的优雅关闭。
package main
import (
"fmt"
"time"
)
func monitor(done <-chan struct{}) {
for {
select {
case <-done:
fmt.Println("Monitor: Stopped.")
return
default:
fmt.Println("Monitor: Doing work...")
time.Sleep(time.Second)
}
}
}
func main() {
done := make(chan struct{}) // 事件通知 Channel
go monitor(done)
time.Sleep(5 * time.Second) // 模拟服务运行
close(done) // 关闭 Channel,通知 monitor 退出
fmt.Println("Main: Shutdown complete.")
}
关键点:
struct{}类型的 Channel 用于通知,不传递数据(节省内存)。close(done)广播通知所有监听者(如monitor)退出。
4. 超时控制与 select 语句
场景:为 Channel 操作设置超时,避免长时间阻塞。
package main
import (
"fmt"
"time"
)
func longTask(ch chan<- string) {
time.Sleep(3 * time.Second) // 模拟长时间任务
ch <- "Task completed"
}
func main() {
ch := make(chan string)
go longTask(ch)
select {
case result := <-ch:
fmt.Println("Result:", result)
case <-time.After(2 * time.Second): // 设置 2 秒超时
fmt.Println("Timeout: Task took too long")
}
}
关键点:
select语句结合time.After实现超时控制。- 若任务超过 2 秒未完成,触发超时分支。
5. 单向 Channel 的应用
场景:限制 Channel 的使用方向,提高代码安全性。
package main
import "fmt"
// 只写 Channel
func producer(ch chan<- int) {
ch <- 42
// ch <- 43 // 错误:无法从只写 Channel 读取
}
// 只读 Channel
func consumer(ch <-chan int) {
fmt.Println(<-ch)
// ch <- 100 // 错误:无法向只读 Channel 写入
}
func main() {
ch := make(chan int)
go producer(ch)
go consumer(ch)
time.Sleep(time.Second)
}
关键点:
- 单向 Channel(
chan<- int/<-chan int)限制函数参数的行为。 - 避免误操作(如向只读 Channel 写入数据)。
6. 限流(Rate Limiting)
场景:通过 Channel 控制并发请求速率,防止资源过载。
package main
import (
"fmt"
"time"
)
func rateLimitedTask(id int, tokenChan <-chan struct{}) {
<-tokenChan // 等待令牌
fmt.Printf("Task %d started\n", id)
time.Sleep(time.Second)
fmt.Printf("Task %d completed\n", id)
}
func main() {
tokenChan := make(chan struct{}, 2) // 最多允许 2 个并发任务
for i := 0; i < 5; i++ {
tokenChan <- struct{}{} // 填充令牌
go rateLimitedTask(i, tokenChan)
}
time.Sleep(5 * time.Second)
}
关键点:
- 使用带缓冲的 Channel(
tokenChan)作为令牌桶。 - 每个任务必须获取一个令牌(
<-tokenChan)才能执行,从而限制并发数。
总结
| 场景 | Channel 的作用 |
|---|---|
| 生产者-消费者 | 解耦生产与消费速度,缓冲数据 |
| 协程池 | 限制并发数量,复用 goroutine |
| 事件通知 | 通过关闭 Channel 广播信号,优雅终止服务 |
| 超时控制 | 结合 select 和 time.After 处理超时 |
| 单向 Channel | 限制 Channel 的使用方向,提高代码安全性 |
| 限流 | 通过缓冲 Channel 控制资源访问速率 |
1076

被折叠的 条评论
为什么被折叠?



