Go 的 goroutine 实现并发而非默认并行,并行度由 GOMAXPROCS 控制;CPU 密集型任务需 worker pool 限流,IO 密集型需防句柄耗尽,内存管理须用 sync.Pool 和预分配避免 GC 压力。
Go 本身不直接提供“并行处理”的抽象层,goroutine 是并发(concurrency)模型,不是并行(parallelism)保证;是否真正并行,取决于 GOMAXPROCS 设置和底层 OS 线程调度。
启动大量 goroutine(比如 go f())只是声明“我想要并发执行”,Go 运行时会把它们调度到有限的 OS 线程上。默认情况下 GOMAXPROCS 等于 CPU 核心数,但若设为 1,哪怕开 1000 个 goroutine,也只会串行切换执行,毫无并行性。
GOMAXPROCS 决定最多几个 OS 线程可同时执行用户 Go 代码(不包括系统调用阻塞线程)runtime.GOMAXPROCS(n) 动态调整,也可用环境变量 GOMAXPROCS=4 启动时设定对计算密集型操作(如矩阵乘法、批量哈希、图像滤波),若不加限制地起 goroutine,会导致所有 goroutine 都争抢 CPU 时间片,但实际并行度仍受 GOMAXPROCS 制约——此时更关键的是避免过度调度和栈分配开销。
go func() { ... }(),尤其当任务量达万级时易触发 GC 压力或内存暴涨func processInParallel(data []int, workers int) []int {
jobs := make(chan int, len(data))
results := make(chan int, len(data))
// 启动固定数量 worker
for w := 0; w < workers; w++ {
go func() {
for n := range jobs {
results <- n * n // 示例:CPU 密集计算
}
}()
}
// 发送任务
for _, d := range data {
jobs <- d
}
close(jobs)
// 收集结果

close(results)
out := make([]int, 0, len(data))
for r := range results {
out = append(out, r)
}
return out}
IO 密集型任务天然适合 goroutine,但需防连接/句柄耗尽
HTTP 请求、文件读写、数据库查询等阻塞操作在 goroutine 中会被自动让出,由运行时挂起该 goroutine 并调度其他就绪的 goroutine——这正是 Go 并发高效的原因。但并行 IO 不等于可无限并发。
http.DefaultTransport.MaxIdleConnsPerHost),超出会排队等待too many open files 错误,需通过 ulimit -n 或程序内限流控制semaphore(如 golang.org/x/sync/semaphore)或带缓冲 channel 控制最大并发请求数高并发下频繁分配小对象(如 []byte、临时结构体)会导致 GC 频繁 STW,成为性能瓶颈。这不是并发模型问题,而是内存管理习惯问题。
sync.Pool 适合复用短期生存的对象,例如 JSON 解析用的 bytes.Buffer 或自定义结构体make([]T, 0, cap) 预分配容量,避免多次扩容拷贝fmt.Sprintf 或字符串拼接,改用 strings.Builder 或预分配 []byte
真正决定并行效果的,从来不是 goroutine 数量,而是你是否控制了资源竞争、是否匹配了硬件并行能力、是否规避了隐藏的串行点(比如共享 map 未加锁、channel 缓冲过小、日志打点同步阻塞)。这些细节比“怎么开 goroutine”重要得多。