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

Go中出现http: wrote more than the declared Content-Length错误及并发优化咨询

问题解决与优化方案

一、解决http: wrote more than the declared Content-Length错误

错误原因

handler将请求封装为Job推入队列后立即返回,此时HTTP服务器会认为请求已处理完成,自动完成响应的收尾流程(如设置默认响应头、标记请求结束)。而worker goroutine在handler返回后才尝试写入响应,此时http.ResponseWriter的状态已被服务器修改,导致写入内容与已声明的Content-Length冲突。

修复步骤

  1. 修改Job结构体,添加同步通道通知handler处理完成:

    type Job struct {
        r    *http.Request
        w    http.ResponseWriter
        done chan struct{} // 通知handler任务处理完成
    }
    
  2. 修改handler函数,初始化done通道并等待worker处理完成后再返回:

    func handler(w http.ResponseWriter, r *http.Request) {
        job := &Job{r, w, make(chan struct{})}
        q.Push(job)
        log.Println("Received request and added job to queue")
        <-job.done // 阻塞等待worker处理完毕
    }
    
  3. 修改doWork函数,处理完成后关闭done通道触发handler返回:

    func doWork(job *Job) {
        defer close(job.done) // 确保无论成功失败都通知handler
        name := job.r.URL.Query().Get("Name")
        if name != "" {
            _, err := job.w.Write([]byte("Hello, " + name))
            if err != nil {
                log.Println("Error writing response:", err)
            }
            log.Println("Response sent: Hello,", name)
        } else {
            http.Error(job.w, "Name parameter is missing or empty", http.StatusBadRequest)
            log.Println("Error: Name parameter is missing or empty")
        }
    }
    

二、并发实现优化

当前基于slice+mutex+cond的队列实现,虽然能工作,但Go语言中用channel实现队列更简洁高效,且天生具备线程安全性,无需手动管理锁和条件变量。以下是优化方案:

1. 用Channel替代自定义Queue结构体

直接使用带缓冲的channel作为任务队列,简化代码:

var jobQueue = make(chan *Job, 100) // 缓冲大小根据业务需求调整
var WORKER_POOL_SIZE = 4

2. 简化Worker与任务调度逻辑

移除原有的Queue结构体、Push/Pop方法,直接通过channel发送和接收任务:

func init() {
    for i := 0; i < WORKER_POOL_SIZE; i++ {
        go worker()
    }
}

func worker() {
    // 从channel循环接收任务,channel关闭时退出
    for job := range jobQueue {
        log.Println("Worker processing job")
        doWork(job)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    job := &Job{r, w, make(chan struct{})}
    jobQueue <- job // 直接向channel发送任务
    log.Println("Received request and added job to queue")
    <-job.done
}

3. 其他优化建议

  • 封装全局变量:将jobQueue和worker数量封装到WorkerPool结构体中,避免全局变量,提高代码可维护性。
  • 添加超时控制:在handler中通过context.WithTimeout设置请求超时,避免worker处理过久导致客户端无限等待:
    func handler(w http.ResponseWriter, r *http.Request) {
        ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
        defer cancel()
    
        job := &Job{r, w, make(chan struct{})}
        select {
        case jobQueue <- job:
            <-job.done
        case <-ctx.Done():
            http.Error(w, "Request timeout", http.StatusRequestTimeout)
        }
    }
    
  • 动态调整worker数量:根据队列任务积压情况动态增减worker数量,不过对于简单场景,固定worker数量已足够。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 02:16:08