Go 语言并发编程的 “倚天剑” 和 “屠龙刀”
goroutine(协程)和 channel(通道);goroutine类似于ts中await的异步效果,channel是goroutine中通信工具。
不要通过共享内存来通信,而要通过通信来共享内存。
具体内容请查看并发 | Golang 中文学习文档!
使用场景
「同时干多件事,不用傻等」
痛点:比如你要调用 3 个不同的微服务接口获取数据,如果串行调用,每个耗时 1s,总共需要 3s。
解决:用 goroutine 让它们同时跑,总共只需要 1s。
package main
import (
"fmt"
"sync"
"time"
)
func fetchAPI(name string, wg *sync.WaitGroup) {
defer wg.Done() // 本 goroutine 完成后计数 -1
fmt.Println("开始调用:", name)
time.Sleep(1 * time.Second) // 模拟网络耗时
fmt.Println("调用完成:", name)
}
func main() {
start := time.Now()
var wg sync.WaitGroup // WaitGroup 解决主进程执行太快导致,子协程没执行完的问题
wg.Add(3) // 需要等待 3 个 goroutine
go fetchAPI("用户服务", &wg)
go fetchAPI("订单服务", &wg)
go fetchAPI("库存服务", &wg)
/*
* 开始调用: 用户服务
* 开始调用: 订单服务
* 开始调用: 库存服务
* 调用完成: 库存服务
* 调用完成: 用户服务
* 调用完成: 订单服务
*/
wg.Wait() // 用于等待子协程结束,否则就阻塞
fmt.Printf("总共耗时: %v\n", time.Since(start))
}
「生产者 - 消费者模式」
痛点:一个模块负责生产数据(比如读文件),另一个模块负责处理数据(比如解析文件)。如果紧耦合在一起,代码很难维护。
解决:用 channel 当 “队列”,生产者只管往里塞,消费者只管往外取。
package main
import "fmt"
// 生产者:只管发送数据到 channel
func writch(ch chan<- int) {
for i := 1; i <= 5; i++ {
fmt.Println("生产了:", i)
ch <- i
}
close(ch)
}
// 消费者:只管从 channel 接收数据
func readch(ch <-chan int) {
for num := range ch {
fmt.Println("消费了:", num)
}
}
func main() {
ch := make(chan int, 3) // 带缓冲的通道,作为仓库
go writch(ch)
readch(ch)
/*
生产了: 1
生产了: 2
生产了: 3
生产了: 4
生产了: 5
消费了: 1
消费了: 2
消费了: 3
消费了: 4
消费了: 5
*/
}
「限流控制:同时只能有 N 个人进房间」
痛点:虽然 goroutine 很轻,但如果同时开 10 万个 goroutine 去查数据库,数据库会直接崩掉。
解决:用带缓冲的 channel 当 “令牌桶”,限制并发数量。
package main
import (
"fmt"
"time"
)
func main() {
// 缓冲大小为 3,意味着同时最多只能有 3 个 goroutine 在执行
limitCh := make(chan bool, 3)
for i := 1; i <= 10; i++ {
go func(id int) {
limitCh <- true // 尝试获取令牌(如果满了,就阻塞在这)
fmt.Printf("任务 %d 开始执行\n", id)
time.Sleep(1 * time.Second) // 模拟耗时操作
fmt.Printf("任务 %d 执行完毕\n", id)
<-limitCh // 释放令牌
}(i)
}
// 简单等待所有任务完成
time.Sleep(4 * time.Second)
//3 个一组分批执行的,这就是限流。
}