You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何使用Semaphore会降低Go网页爬取程序的运行速度?

问题分析与解决方案

性能下降的核心原因

你的爬虫程序瓶颈根本不在CPU,而是网络IO。原程序创建大量goroutine时,这些goroutine大部分时间都在等待网络响应(属于阻塞的IO操作)。Go的调度器会自动把空闲的线程(M)分配给其他可运行的goroutine,所以虽然goroutine数量多,但并不会占满所有CPU核心线程,反而能充分利用等待IO的间隙并发处理更多请求。

而你用semaphore把并发数限制为runtime.GOMAXPROCS(0)(即CPU核心数),相当于人为把并发请求数降到了CPU核心级别——但网络IO的延迟远高于CPU处理时间,这直接导致单位时间内处理的请求数大幅减少,总耗时自然翻倍。

控制goroutine数量同时保性能的方案

不要把并发数绑定到CPU核心数,而是根据网络请求的承载能力设置一个合理的并发值(比如20、50甚至100,具体看目标网站的反爬策略和你的网络带宽)。

推荐用带缓冲的通道实现并发控制,比semaphore更轻量直观:

package main

import (
    "fmt"
    "net/http"
    "sync"
)

func main() {
    // 模拟1000个待爬取URL
    urls := make([]string, 1000)
    for i := range urls {
        urls[i] = fmt.Sprintf("https://example.com/page/%d", i+1)
    }

    // 设置合理的并发数,根据实际情况调整
    maxConcurrency := 50
    sem := make(chan struct{}, maxConcurrency)
    var wg sync.WaitGroup

    for _, url := range urls {
        wg.Add(1)
        // 占用一个并发名额
        sem <- struct{}{}
        go func(u string) {
            defer wg.Done()
            // 释放并发名额
            defer func() { <-sem }()

            // 发起请求并处理
            resp, err := http.Get(u)
            if err != nil {
                fmt.Printf("请求%s失败: %v\n", u, err)
                return
            }
            defer resp.Body.Close()

            // 这里添加页面解析、数据提取等逻辑
        }(url)
    }

    wg.Wait()
}

为什么这个方案有效

带缓冲通道的并发控制逻辑中,goroutine在等待网络响应时会被Go调度器挂起,空闲下来的线程会去处理其他就绪的goroutine。所以即使并发数远大于CPU核心数,也不会浪费CPU资源,同时又能控制goroutine的总量(避免无限制创建导致的内存占用过高),最终性能能接近原程序的水平。

内容的提问来源于stack exchange,提问作者akopyl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:11:10