You need to enable JavaScript to run this app. Go HTTP服务优雅关闭触发Race Condition竞态问题如何修复
优惠活动
大模型
产品
解决方案
定价
更多

Go HTTP服务优雅关闭触发Race Condition竞态问题如何修复

竞态产生的原因

你代码中的数据竞争来源于serve函数的返回值变量err被两个goroutine并发读写,未做同步:

  • 你启动的后台goroutine会执行err = srv.ListenAndServe()对外部的err变量赋值
  • 主goroutine在收到停止信号后会执行err = srv.Shutdown(ctxShutDown)对同一个err变量赋值
    两个goroutine同时操作同一块内存地址的变量,就触发了竞态检测。
修复方案

只需要把后台goroutine中用到的err替换为goroutine内部的局部变量即可,不需要修改外部的返回值err:
srv.ListenAndServe只有两种返回场景:

  1. 服务启动失败(比如端口被占用),此时直接log.Fatal退出进程,不需要把错误赋值给外部err
  2. 服务被Shutdown主动关闭,返回固定的http.ErrServerClosed,这个返回值不需要额外处理,后续Shutdown方法的返回值才是serve函数需要返回的错误。

修改的核心代码片段:

// 原代码
go func() {
    if err = srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatalf("listen:%+s\n", err)
    }
}()

// 修改后代码
go func() {
    // 使用内部局部变量listenErr,不修改外部的返回值err
    listenErr := srv.ListenAndServe()
    if listenErr != nil && listenErr != http.ErrServerClosed {
        log.Fatalf("listen:%+s\n", listenErr)
    }
}()
完整修复后代码
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"
)

func serve(ctx context.Context) (err error) {
    mux := http.NewServeMux()
    mux.Handle("/", http.HandlerFunc(
        func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "okay")
        },
    ))

    srv := &http.Server{
        Addr:    ":8000",
        Handler: mux,
    }

    go func() {
        listenErr := srv.ListenAndServe()
        if listenErr != nil && listenErr != http.ErrServerClosed {
            log.Fatalf("listen:%+s\n", listenErr)
        }
    }()

    log.Printf("server started")

    <-ctx.Done()

    log.Printf("server stopped")

    ctxShutDown, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    if err = srv.Shutdown(ctxShutDown); err != nil {
        log.Fatalf("server Shutdown Failed:%+s", err)
    }

    log.Printf("server exited properly")

    if err == http.ErrServerClosed {
        err = nil
    }

    return
}

func main() {
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)

    ctx, cancel := context.WithCancel(context.Background())

    go func() {
        oscall := <-c
        log.Printf("system call:%+v", oscall)
        cancel()
    }()

    if err := serve(ctx); err != nil {
        log.Printf("failed to serve:+%v\n", err)
    }
}

修复后重新执行go run -race main.go,触发SIGINT信号就不会再出现竞态提示。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.16 12:06:05