Go 并发编程入门
Go 的并发方法是其最独特和最受欢迎的特性之一。Go 引入了 goroutine 和 channel,无需依赖传统的线程模型,就能让编写并发代码变得轻而易举。[1]
Goroutine:轻量级并发
创建 goroutine 非常简单,只需在函数调用前加上 go 关键字:[2]
func main() {
go sayHello()
go fetchData("https://api.example.com/data")
time.Sleep(time.Second)
}
与 OS 线程不同,goroutine 被多路复用到较少的 OS 线程上。Go 运行时处理调度,允许数千甚至数百万个 goroutine 并发运行而不会出现问题。[3]

Channel:Goroutine 之间的通信
如果 goroutine 是参与者,那么 channel 就是连接它们的消息总线。Channel 提供类型安全的通信和同步:[4]
ch := make(chan string)
go func() {
result := doWork()
ch <- result // 发送到 channel
}()
response := <-ch // 从 channel 接收
这种模式消除了一整类 bug。对于基本的生产者-消费者场景,你不再需要互斥锁和条件变量——channel 为你处理同步。
Select 语句
当你有多个 channel 时,select 可以让你同时等待所有 channel:[5]
select {
case msg1 := <-ch1:
fmt.Println("从 channel 1 收到消息:", msg1)
case msg2 := <-ch2:
fmt.Println("从 channel 2 收到消息:", msg2)
case <-time.After(time.Second):
fmt.Println("超时")
}
实战示例:并发爬虫
这是一个真实的例子——并发网页爬虫,同时获取多个页面:
func fetchAll(urls []string) []string {
results := make(chan string, len(urls))
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, _ := http.Get(u)
body, _ := io.ReadAll(resp.Body)
results <- string(body)
}(url)
}
go func() {
wg.Wait()
close(results)
}()
var output []string
for r := range results {
output = append(output, r)
}
return output
}
结论
Go 的并发模型成功源于其理念:通过共享内存来通信。与其担心锁和互斥锁,不如通过 channel 传递数据。
这种方法从简单脚本到生产系统都适用。Google、Dropbox 和 Twitch 等公司使用 Go,正是因为其并发模型使复杂的并行系统变得易于理解。
下次你需要处理多个并发操作时,考虑使用 goroutine 和 channel。未来的你会感谢现在的你。
Comments