【问题标题】:How can I prevent a data race when adding handlers in a goroutine?在 goroutine 中添加处理程序时如何防止数据竞争?
【发布时间】:2018-10-10 15:04:18
【问题描述】:

在我用 golang 编写的 HTTP 应用程序中,我有一些依赖于 3rd 方服务(和供应商代码)的路由在我真正能够注册路由之前做一些工作。这可能会失败或需要重试,但我仍然希望应用程序在此过程可能正在进行时响应其他请求。

这意味着我在我的main func 生成的goroutines 中的http.DefaultServeMux 上注册处理程序。这按预期工作,但我会发现我的测试现在抱怨数据竞争。

重现的最小案例如下所示:

package main

import (
    "log"
    "net/http"
)

func main() {
    go func() {
        http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("hello"))
        })
    }()
    srv := http.Server{
        Addr: ":3000",
    }
    log.Fatal(srv.ListenAndServe())
}

通过如下测试:

package main

import (
    "io/ioutil"
    "net/http"
    "os"
    "testing"
    "time"
)

func TestMain(m *testing.M) {
    go main()
    time.Sleep(time.Second)
    os.Exit(m.Run())
}

func TestHello(t *testing.T) {
    t.Run("default", func(t *testing.T) {
        res, err := http.DefaultClient.Get("http://0.0.0.0:3000/hello")
        if err != nil {
            t.Fatalf("Calling /hello returned %v", err)
        }
        if res.StatusCode != http.StatusOK {
            b, _ := ioutil.ReadAll(res.Body)
            defer res.Body.Close()
            t.Errorf("Expected /hello to return 200 response, got %v with body %v", res.StatusCode, string(b))
        }
    })
}

将显示以下输出:

==================
WARNING: DATA RACE
Read at 0x000000a337d8 by goroutine 14:
  net/http.(*ServeMux).shouldRedirect()
      /usr/local/go/src/net/http/server.go:2239 +0x162
  net/http.(*ServeMux).redirectToPathSlash()
      /usr/local/go/src/net/http/server.go:2224 +0x64
  net/http.(*ServeMux).Handler()
      /usr/local/go/src/net/http/server.go:2293 +0x184
  net/http.(*ServeMux).ServeHTTP()
      /usr/local/go/src/net/http/server.go:2336 +0x6d
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2694 +0xb9
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1830 +0x7dc

Previous write at 0x000000a337d8 by goroutine 8:
  net/http.(*ServeMux).Handle()
      /usr/local/go/src/net/http/server.go:2357 +0x216
  net/http.(*ServeMux).HandleFunc()
      /usr/local/go/src/net/http/server.go:2368 +0x62
  net/http.HandleFunc()
      /usr/local/go/src/net/http/server.go:2380 +0x68
  github.com/m90/test.main.func1()
      /home/frederik/projects/go/src/github.com/m90/test/main.go:10 +0x4f

Goroutine 14 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/go/src/net/http/server.go:2795 +0x364
  net/http.(*Server).ListenAndServe()
      /usr/local/go/src/net/http/server.go:2711 +0xc4
  github.com/m90/test.main()
      /home/frederik/projects/go/src/github.com/m90/test/main.go:17 +0xb6

Goroutine 8 (finished) created at:
  github.com/m90/test.main()
      /home/frederik/projects/go/src/github.com/m90/test/main.go:9 +0x46
==================

据我了解,在堆栈跟踪中阅读 the code of package http net/http.(*ServeMux).Handler() 不会锁定保护处理程序映射的互斥锁,因为它希望这将由 net/http.(*ServeMux).handler() 完成,在我的场景中不会被调用。

我在做不应该做的事情吗?这是标准库的问题吗?我在 goroutine 中附加处理程序的方式有问题吗?

【问题讨论】:

  • 您应该正常注册您的处理程序(即不在 Go 例程中),并在处理程序中添加线程安全检查以确保启动第 3 方库。如果启动,请将请求传递给第 3 方处理程序。
  • 这就是我想做的,但麻烦的是库(github.com/go-telegram-bot-api/telegram-bot-api)无论如何都会调用http.HandleFunc(在默认多路复用器上注册自己)。再说一次,看看它在引擎盖下做了什么,我应该能够自己重新实现它(跳过HandleFunc 调用)并按照你的建议做。会试试的。
  • 老实说,这可以说是 ServeMux 中的固有种族(即语言错误)。路由映射应该是互斥保护的,通常是,但 shouldRedirect 可以从 ServeMux.Handler() 中的多个路径调用而不受互斥保护。
  • @Kaedys 看起来它已经在 2018 年 3 月解决了:github.com/golang/go/pull/23994

标签: go concurrency goroutine


【解决方案1】:

这似乎是 http 包本身的问题,已解决 via this Pull Request

截至 2018 年 4 月,该补丁不包含在 go1.10.1 中,但它应该与 go1.11 一起发布

【讨论】:

    猜你喜欢
    • 2021-05-04
    • 2019-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多