go - 在 goroutine 中添加处理程序时如何防止数据竞争?
问题描述
在我用 golang 编写的 HTTP 应用程序中,我有一些依赖 3rd 方服务(和供应商代码)的路由在我真正能够注册路由之前做一些工作。这可能会失败或需要重试,但我仍然希望应用程序在此过程可能正在进行时响应其他请求。
这意味着我正在http.DefaultServeMux
从我的main
func 生成的 goroutine 中注册处理程序。这按预期工作,但我会发现我的测试现在抱怨数据竞争。
重现的最小案例如下所示:
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
==================
据我了解,在堆栈跟踪中阅读包 http 的代码 net/http.(*ServeMux).Handler()
不会锁定保护处理程序映射的互斥锁,因为它希望net/http.(*ServeMux).handler()
在我的场景中不会被调用来完成此操作。
我在做不应该做的事情吗?这是标准库的问题吗?我在 goroutine 中附加处理程序的方式有问题吗?
解决方案
这似乎是包 http 本身的一个问题,可以通过这个 Pull Request解决。
截至 2018 年 4 月,该补丁不包含在 中go1.10.1
,但它应该随go1.11
推荐阅读
- ruby-on-rails - 修复 'Gem::Requirement::BadRequirementError: Illformed requirements [">=1 <2"]' 错误
- react-native - 当我们的应用程序已经在后台由于诸如 SignIn with Google 之类的提示时如何处理应用程序状态更改
- python - 将 JSON 数据转换为 Python 数据框
- html - 绝对定位元素在其固定定位的父元素中被切断
- java - 多代理框架 SACI 未找到错误
- git - Git:推送本地回购无关历史错误时出错
- python - Rethinkdb:“连接已关闭”
- shell - 使用用户名和密码连接 ADB 代理
- laravel - 我想使用 laravel 从数据库中获取聊天消息
- javascript - 如何将数组值传递给javascript函数?PHP Laravel