goroutine 启动后立即执行,主流程不阻塞但需显式同步;必须用 sync.WaitGroup 或 channel 等待结果,禁止依赖 time.Sleep;多个 goroutine 写共享变量会引发 panic,应通过 channel 安全传递 Result 结构体。

goroutine 启动后立即执行,不等主 goroutine
并发搜索的核心是让多个搜索任务同时跑,而不是串行等待。用 go search(keyword) 启动后,函数立刻在新 goroutine 中执行,主流程不会阻塞——但这也意味着,如果主函数结束,所有子 goroutine 会被强制终止。常见错误是写完 go search("a")、go search("b") 就直接 return,结果什么结果都收不到。
必须显式等待:用 sync.WaitGroup 计数,或通过 channel 收集结果并阻塞主 goroutine。别依赖 time.Sleep,它不可靠且掩盖了同步问题。
- 每次
go前调用wg.Add(1) - 每个搜索函数末尾必须有
wg.Done() - 主 goroutine 中用
wg.Wait()阻塞直到全部完成
用 channel 安全传递搜索结果,避免共享变量竞争
多个 goroutine 往同一个切片或 map 写数据会引发 panic 或数据错乱,比如 fatal error: concurrent map writes。正确做法是让每个 goroutine 把结果发到一个共用的 chan Result,由主 goroutine 统一接收。channel 天然线程安全,且能自然实现“生产-消费”解耦。
注意 channel 容量:如果搜索量大、单次耗时长,用无缓冲 channel(make(chan Result))会让发送方阻塞,拖慢整体吞吐;可考虑带缓冲 channel(如 make(chan Result, 100)),但缓冲区不能无限大,需结合内存和下游处理速度权衡。
立即学习“go语言免费学习笔记(深入)”;
- 定义结构体
type Result struct { Keyword string; Data []byte; Err error } - 启动 goroutine 时传入该 channel:
go search("go", resultsCh) - 主 goroutine 用
for i := 0; i 按启动数量收结果
超时控制必须用 context.WithTimeout,不能只靠 select + time.After
搜索可能卡死(比如 HTTP 请求没响应、数据库连接 hang 住),必须设全局超时。只用 select { case 无法取消正在运行的 goroutine,只是主流程跳出了等待——后台 goroutine 还在跑,浪费资源甚至导致连接泄漏。
正确方式是把 context.Context 传进每个搜索函数,在关键阻塞点(如 http.Client.Do(req.WithContext(ctx))、db.QueryRowContext(ctx, ...))使用带 context 的方法。一旦超时,context 被 cancel,这些底层调用会立即返回错误。
- 创建带超时的 context:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer
cancel()防止 context 泄漏 - 所有 I/O 操作必须显式传入
ctx,否则超时无效
goroutine 数量不是越多越好,要配合实际 IO 类型限流
HTTP 搜索本质是 IO 密集型,不是 CPU 密集型。盲目起几百个 goroutine 可能导致文件描述符耗尽(too many open files)、服务端限流拒绝、或 DNS 查询失败。Go 默认的 http.DefaultClient 对同一 host 最多复用 100 个连接,超出会排队。
合理做法是用 semaphore 控制并发数(例如限制最多 20 个并发请求),或者直接配置 http.Transport 的 MaxConnsPerHost 和 MaxIdleConns。本地 CPU 搜索(如遍历目录)才适合接近 runtime.NumCPU() 的并发数。
- 简单限流可用带缓冲 channel 当信号量:
sem := make(chan struct{}, 20),每个 goroutine 先sem ,结束后 <code><-sem - HTTP 场景优先调优
http.Transport,而非堆 goroutine - 用
pprof观察 goroutine 数量突增,往往是泄漏或误用信号量
实际最难的不是启动 goroutine,而是确保每个环节都尊重上下文、正确释放资源、并在错误路径上做 cleanup——比如 defer 关闭 HTTP body、及时 cancel 子 context、channel 发送前检查是否已 closed。这些细节不写进代码,迟早出问题。


















