1. 什么是 Goroutine?
Goroutine 是 Go 语言并发编程的核心概念,可以理解为一种轻量级的线程。与操作系统线程(OS Thread)相比,Goroutine 的创建和切换成本极低,这使得 Go 程序能够轻松创建成千上万个并发执行单元。
Goroutine 的主要特点:
- 轻量级:初始栈大小仅 2KB(可动态增长),远小于线程的 MB 级栈
- 低成本创建:创建开销极小,可轻松创建数十万个 Goroutine
- 由 Go 运行时调度:不直接映射到操作系统线程,由 Go 自己的调度器管理
- 通信通过 Channel:遵循 "不要通过共享内存来通信,而要通过通信来共享内存" 的原则
2. Goroutine 的基本用法
2.1 启动 Goroutine
使用 go 关键字即可启动一个 Goroutine:
go
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from Goroutine!")
}
func main() {
// 启动一个 Goroutine
go sayHello()
// 主 Goroutine 继续执行
fmt.Println("Hello from main Goroutine!")
// 等待一下,让 sayHello 有机会执行
time.Sleep(100 * time.Millisecond)
}
2.2 匿名函数 Goroutine
也可以直接使用匿名函数启动 Goroutine:
go
package main
import "fmt"
func main() {
// 使用匿名函数启动 Goroutine
go func(name string) {
fmt.Printf("Hello, %s!\n", name)
}("Gopher")
// 等待 Goroutine 执行
time.Sleep(50 * time.Millisecond)
}
3. Goroutine 与主程序同步
由于 Goroutine 是异步执行的,我们需要确保主程序等待 Goroutine 完成。常用的同步方式有:
3.1 使用 sync.WaitGroup
go
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 完成后通知 WaitGroup
fmt.Printf("Worker %d starting\n", id)
// 模拟工作
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // 增加等待计数
go worker(i, &wg)
}
wg.Wait() // 等待所有 Goroutine 完成
fmt.Println("All workers completed")
}
3.2 使用 Channel 同步
go
package main
import "fmt"
func worker(id int, done chan bool) {
fmt.Printf("Worker %d working...\n", id)
done <- true // 发送完成信号
}
func main() {
done := make(chan bool, 3)
for i := 1; i <= 3; i++ {
go worker(i, done)
}
// 等待所有 worker 完成
for i := 1; i <= 3; i++ {
<-done
}
fmt.Println("All workers completed")
}
4. Goroutine 调度原理
4.1 G-M-P 模型

Go 的调度器采用 G-M-P 模型:
- G (Goroutine):表示一个 Goroutine,包含栈、程序计数器等信息
- M (Machine):代表操作系统线程,真正执行代码的实体
- P (Processor):逻辑处理器,管理 Goroutine 队列
4.2 调度特点
- 工作窃取(Work Stealing):空闲的 P 会从其他 P 的队列中"窃取" Goroutine
- 抢占式调度:Go 1.14 开始支持基于信号的抢占,防止 Goroutine 长时间占用 CPU
- 网络轮询器:独立的网络 I/O 处理,避免阻塞 Goroutine
5. 常见模式与最佳实践
5.1 生产者-消费者模式
go
package main
import (
"fmt"
"time"
)
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
fmt.Printf("Produced: %d\n", i)
time.Sleep(100 * time.Millisecond)
}
close(ch)
}
func consumer(id int, ch <-chan int) {
for item := range ch {
fmt.Printf("Consumer %d received: %d\n", id, item)
time.Sleep(200 * time.Millisecond)
}
}
func main() {
ch := make(chan int, 3)
// 启动生产者
go producer(ch)
// 启动多个消费者
for i := 1; i <= 3; i++ {
go consumer(i, ch)
}
time.Sleep(2 * time.Second)
}
5.2 扇出/扇入模式
go
package main
import (
"fmt"
"sync"
)
// 扇出:一个输入 channel,多个 Goroutine 处理
func fanOut(input <-chan int, numWorkers int) []<-chan int {
outputs := make([]<-chan int, numWorkers)
for i := 0; i < numWorkers; i++ {
ch := make(chan int)
outputs[i] = ch
go func(workerID int, out chan<- int) {
for val := range input {
out <- val * workerID
}
close(out)
}(i+1, ch)
}
return outputs
}
// 扇入:多个 channel 合并为一个
func fanIn(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for val := range c {
out <- val
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
6. 注意事项与常见陷阱
6.1 Goroutine 泄漏
忘记关闭 channel 或 Goroutine 无法退出会导致泄漏:
go
// ❌ 错误示例:Goroutine 泄漏
func leakyFunction() {
ch := make(chan int)
go func() {
// 这个 Goroutine 永远不会退出
for {
select {
case <-ch:
// 没有退出逻辑
}
}
}()
}
// ✅ 正确做法:使用 context 控制生命周期
func properFunction(ctx context.Context) {
ch := make(chan int)
go func() {
for {
select {
case <-ctx.Done():
return // 可以正常退出
case val := <-ch:
fmt.Println(val)
}
}
}()
}
6.2 数据竞争
多个 Goroutine 同时访问共享数据可能导致数据竞争:
go
// ❌ 存在数据竞争
var counter int
for i := 0; i < 1000; i++ {
go func() {
counter++ // 数据竞争!
}()
}
// ✅ 使用 sync.Mutex 保护
var (
counter int
mu sync.Mutex
)
for i := 0; i < 1000; i++ {
go func() {
mu.Lock()
counter++
mu.Unlock()
}()
}
// ✅ 更好的做法:使用 sync/atomic
var counter int32
for i := 0; i < 1000; i++ {
go func() {
atomic.AddInt32(&counter, 1)
}()
}
7. 性能调优建议
- 控制 Goroutine 数量:使用 worker pool 模式,避免无限制创建
- 合理设置 GOMAXPROCS:默认等于 CPU 核心数,可根据场景调整
- 使用缓冲 channel:适当缓冲可以减少 Goroutine 阻塞
- 避免频繁创建/销毁:考虑复用 Goroutine(如 worker pool)
- 监控 Goroutine 数量 :使用
runtime.NumGoroutine()监控
8. 总结
Goroutine 是 Go 语言并发编程的基石,它的轻量级特性使得编写高并发程序变得简单高效。掌握 Goroutine 的正确使用方式,结合 Channel 和同步原语,可以构建出既安全又高效的并发系统。
关键要点回顾:
- 使用
go关键字启动 Goroutine - 通过 Channel 或 sync 包进行同步
- 注意避免 Goroutine 泄漏和数据竞争
- 理解 G-M-P 调度模型有助于性能优化
- 遵循 Go 的并发哲学:"通过通信共享内存"
随着对 Goroutine 的深入理解,你将能够充分利用 Go 语言的并发优势,构建出高性能的分布式系统和网络服务。