应控制goroutine并发数,用channel信号量或worker pool限流;闭包需传参防变量共享;错误管理优先用errgroup.Group统一处理。

用 goroutine 启动并行任务,但别直接裸奔
Go 里最常用的并行方式就是起 goroutine,但它不是“开越多越快”的银弹。比如批量调用 HTTP 接口、处理一批文件、或并发查询数据库,你写 go doTask(item) 很容易,但没控制数量就可能打爆内存或触发限流。
实际做法是配合 sync.WaitGroup 等待全部完成,并用带缓冲的 channel 或 worker pool 控制并发数:
var wg sync.WaitGroup
sem := make(chan struct{}, 10) // 最多 10 个并发
for _, item := range tasks {
wg.Add(1)
go func(t Task) {
defer wg.Done()
sem <- struct{}{} // 获取信号量
defer func() { <-sem }() // 释放
process(t)
}(item)
}
wg.Wait()
- 不加限制时,
goroutine数量可能瞬间上千,GC 压力陡增 -
sem这种 channel 信号量比sync.Mutex更轻量,适合纯计数场景 - 注意闭包捕获变量:必须把
item作为参数传入匿名函数,否则所有 goroutine 可能共享最后一个值
用 errgroup.Group 统一管理错误和生命周期
原生 sync.WaitGroup 不支持错误传播,一旦某个任务出错,其他还在跑,你得自己设标志位或加锁判断。而 errgroup.Group(来自 golang.org/x/sync/errgroup)天然支持“任一出错即取消其余”。
适用场景:需要强一致性失败语义的任务链,比如批量写入 ES、同步多个下游服务:
立即学习“go语言免费学习笔记(深入)”;
g, ctx := errgroup.WithContext(context.Background())
for _, url := range urls {
url := url // 防止闭包问题
g.Go(func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
})
}
if err := g.Wait(); err != nil {
log.Printf("at least one request failed: %v", err)
}
-
WithContext返回的ctx会在任意子任务返回错误时被 cancel,可用于提前中断长耗时操作 - 它内部用了
sync.WaitGroup+sync.Once,线程安全,无需额外加锁 - 如果任务本身不接受
context.Context,记得在函数体内用select { case 主动响应取消
拆分任务时优先按数据边界而非固定数量切片
很多人习惯把一个大 slice 按每 100 个元素切一块,然后并发处理。但这在真实场景中常导致负载不均——比如某些分块里全是大文件、慢接口或重计算项,结果大部分 goroutine 早结束了,只剩一两个卡着。
AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。它不是新的编程语言,而是一种使用现有标准的新方法,最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。《php中级教程之ajax技术》带你快速
更稳妥的做法是按“工作单元”本身拆分,而不是数组下标:
- 对文件处理:按文件粒度并发,而不是按字节范围切分(除非你在做 mmap 或流式解析)
- 对数据库查询:用
IN批量查不如按主键范围分页(如id BETWEEN ? AND ?),避免单条 SQL 过长或命中率低 - 对 API 调用:若后端支持批量接口(如
/batch?ids=a,b,c),优先用批量,而不是把单 ID 请求并发化
简单粗暴的等长切片只适合各单元耗时方差极小的场景,比如纯内存 JSON 解析。
runtime.GOMAXPROCS 一般不用手动调,但要注意 CGO 场景
默认情况下,Go 运行时会把 GOMAXPROCS 设为 CPU 核心数,这对绝大多数纯 Go 并发任务已足够。强行设高不会提升吞吐,反而增加调度开销。
唯一需要关注它的场景是启用了 CGO 且调用大量阻塞式 C 函数(如某些加密库、旧版 SQLite 驱动):
- 每个阻塞的 CGO 调用会占用一个 OS 线程,且该线程无法被 Go 调度器复用
- 此时若
GOMAXPROCS太小,新 goroutine 可能因无可用 P 而饿死 - 解决方案不是狂拉
GOMAXPROCS,而是改用非阻塞替代方案,或用runtime.LockOSThread()+ 单独线程池隔离 CGO
大多数现代 Go 项目根本碰不到这个问题,但一旦遇到“并发数上不去、CPU 却很低”的诡异现象,就得回头查是不是 CGO 在暗处拖后腿。









