Goroutine 详细解释
goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理。它是实现并发编程的核心机制。
核心概念
| 特性 | 说明 |
|---|---|
| 轻量级 | 创建成本极低,一个程序可以轻松创建数百万个 goroutine |
| 并发执行 | 多个 goroutine 可以同时运行 |
| 由运行时管理 | Go 运行时自动调度 goroutine 到 CPU 核心上 |
| 非抢占式 | goroutine 只在特定点(如 I/O、channel 操作)让出控制权 |
基本用法
1. 创建 Goroutine
go
package main
import (
"fmt"
"time"
)
func main() {
// 普通函数调用(同步)
sayHello("Alice")
// 启动 goroutine(异步)
go sayHello("Bob")
// 主 goroutine 需要等待子 goroutine 完成
time.Sleep(1 * time.Second)
}
func sayHello(name string) {
fmt.Printf("Hello, %s\n", name)
}
输出:
Hello, Alice
Hello, Bob
2. 多个 Goroutine
go
package main
import (
"fmt"
"time"
)
func main() {
for i := 1; i <= 5; i++ {
go func(id int) {
fmt.Printf("Goroutine %d 开始\n", id)
time.Sleep(time.Duration(id) * time.Second)
fmt.Printf("Goroutine %d 完成\n", id)
}(i)
}
// 等待所有 goroutine 完成
time.Sleep(6 * time.Second)
fmt.Println("主程序结束")
}
输出:
Goroutine 1 开始
Goroutine 2 开始
Goroutine 3 开始
Goroutine 4 开始
Goroutine 5 开始
Goroutine 1 完成
Goroutine 2 完成
Goroutine 3 完成
Goroutine 4 完成
Goroutine 5 完成
主程序结束
go func() 和 go sayHello("Bob") 的区别
| 特性 | go func() |
go sayHello("Bob") |
|---|---|---|
| 类型 | 匿名函数 | 命名函数 |
| 定义位置 | 调用处定义 | 提前定义 |
| 代码量 | 适合简短逻辑 | 适合复杂逻辑 |
| 可重用性 | 不可重用 | 可重用 |
| 闭包 | 可访问外部变量 | 需要参数传递 |
Goroutine vs 线程
| 特性 | Goroutine | 线程 |
|---|---|---|
| 内存占用 | ~2KB | ~1-2MB |
| 创建速度 | 极快 | 较慢 |
| 数量 | 可创建百万级 | 通常数百个 |
| 切换成本 | 低 | 高 |
| 管理 | Go 运行时 | 操作系统 |
与 Channel 结合
go
package main
import (
"fmt"
)
func main() {
// 创建 channel
results := make(chan string)
// 启动 goroutine
go func() {
results <- "任务 1 完成"
}()
go func() {
results <- "任务 2 完成"
}()
// 接收结果
fmt.Println(<-results)
fmt.Println(<-results)
}
输出:
任务 1 完成
任务 2 完成
常见模式
1. 使用 sync.WaitGroup 等待完成
go
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d 执行\n", id)
}(i)
}
wg.Wait()
fmt.Println("所有 goroutine 完成")
}
2. 并发 HTTP 请求
go
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
urls := []string{
"https://example.com",
"https://google.com",
"https://github.com",
}
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, err := http.Get(u)
if err != nil {
fmt.Printf("请求 %s 失败: %v\n", u, err)
} else {
fmt.Printf("请求 %s 成功,状态码: %d\n", u, resp.StatusCode)
}
}(url)
}
wg.Wait()
}
注意事项
⚠️ 常见问题:
- 主 goroutine 过早退出:子 goroutine 还未完成就结束了
- 竞态条件:多个 goroutine 同时修改共享变量
- 死锁:goroutine 互相等待
✅ 最佳实践:
- 使用
sync.WaitGroup或channel等待 goroutine 完成 - 使用
sync.Mutex保护共享数据 - 避免在 goroutine 中直接修改外部变量(使用参数传递)