Go 如何通过编程证明此代码具有竞争条件?

Go 如何通过编程证明此代码具有竞争条件?,go,race-condition,httpserver,Go,Race Condition,Httpserver,我被告知这段代码有一个设计上的竞争条件,尽管我可能会尝试,但我无法证明它有竞争条件 func (h *handler) loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h.Log = log.WithFields(log.Fields{ "method"

我被告知这段代码有一个设计上的竞争条件,尽管我可能会尝试,但我无法证明它有竞争条件

func (h *handler) loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h.Log = log.WithFields(log.Fields{
            "method":     r.Method,
            "requestURI": r.RequestURI,
        })
        next.ServeHTTP(w, r)
    })
}
我尝试了
go build-race
,然后运行二进制代码:
PORT=3000./main
&加载像
hey-n 10000-c 200这样的创建者http://localhost:3000

下面是代码的其余部分:


如果我不能证明它有竞争条件,我可以假设设置
h.Log
是安全的吗?

想象一下,您在几乎相同的时间收到两个到达同一处理程序的入站连接。第一个连接开始运行:

h.Log = log.WithFields(log.Fields{
    "method":     rFirst.Method,
    "requestURI": rFirst.RequestURI,
})
但是等等!第二个连接出现了!也许运行时想要挂起这个goroutine并启动第二个连接。然后

h.Log = log.WithFields(log.Fields{
    "method":     rSecond.Method,
    "requestURI": rSecond.RequestURI,
})
next.ServeHTTP(wSecond, rSecond)
呸……这就完了。让我们回到我们的第一次狂欢

// What's in h.Log now, with this sequence of events?
next.ServeHTTP(wFirst, rFirst)
或者

您的第二组示例不会更改
h.Log
的值,但会对其调用方法。在大多数情况下,这可能安全,也可能不安全。包含神奇的短语:“可以从多个goroutine同时使用记录器”。(如果您实际导入了
“github.com/sirupsen/logrus”
作为
log
,)

我可以假设设置h.Log是安全的吗


没有一个
sync.Mutex
或类似的东西来保护它,不是真的。您肯定不能保证,如果您在第1行设置它,那么如果其他goroutine可能正在更改它,它在第2行将具有相同的值。有一个更精确的定义,即什么样的副作用保证在出现时是可见的。

有一种编程方式,您必须做两件事:

  • 重现活泼的状态
  • 并在启动
    go
    工具时使用
    -race
    选项
最好是为它编写一个单元测试,这样测试也是可复制的,并且在每次构建/部署时自动运行/检查

好的,那么如何复制它呢

只需编写一个测试,启动两个goroutine,一个调用
索引
处理程序,另一个调用
关于
处理程序,故意不同步,这就是触发竞争检测器的原因

使用该包可以轻松地测试处理程序。给你一个准备就绪的服务器,用你传递给它的处理程序“武装”起来

下面是一个简单的测试示例,它将触发竞争条件。将它放在名为
main\u test.go
的文件中,在
main.go
文件旁边:

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "sync"
    "testing"

    "github.com/gorilla/mux"
)

func TestRace(t *testing.T) {
    h := New()
    app := mux.NewRouter()
    app.HandleFunc("/", h.index)
    app.HandleFunc("/about", h.about)
    app.Use(h.loggingMiddleware)

    server := httptest.NewServer(app)
    defer server.Close()

    wg := &sync.WaitGroup{}
    for _, path := range []string{"/", "/about"} {
        path := path
        wg.Add(1)
        go func() {
            defer wg.Done()
            req, err := http.NewRequest(http.MethodGet, server.URL+path, nil)
            fmt.Println(server.URL + path)
            if err != nil {
                panic(err)
            }
            res, err := http.DefaultClient.Do(req)
            if err != nil {
                panic(err)
            }
            defer res.Body.Close()
        }()
    }
    wg.Wait()
}
你必须用它来运行它

go test -race
示例输出为:

http://127.0.0.1:33007/
http://127.0.0.1:33007/about
==================
WARNING: DATA RACE
Write at 0x00c000098030 by goroutine 17:
  play.(*handler).loggingMiddleware.func1()
      /home/icza/tmp/gows/src/play/main.go:16 +0x1ce
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1964 +0x51
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2741 +0xc4
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1847 +0x80a

Previous write at 0x00c000098030 by goroutine 16:
  play.(*handler).loggingMiddleware.func1()
      /home/icza/tmp/gows/src/play/main.go:16 +0x1ce
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1964 +0x51
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2741 +0xc4
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1847 +0x80a

Goroutine 17 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/go/src/net/http/server.go:2851 +0x4c5
  net/http/httptest.(*Server).goServe.func1()
      /usr/local/go/src/net/http/httptest/server.go:280 +0xac

Goroutine 16 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/go/src/net/http/server.go:2851 +0x4c5
  net/http/httptest.(*Server).goServe.func1()
      /usr/local/go/src/net/http/httptest/server.go:280 +0xac
==================
2019/01/06 14:58:50  info index                     method=GET requestURI=/
2019/01/06 14:58:50  info about                     method=GET requestURI=/about
--- FAIL: TestRace (0.00s)
    testing.go:771: race detected during execution of test
FAIL
exit status 1
FAIL    play    0.011s
测试失败,表明存在数据竞争

注意事项:


sync.WaitGroup
的同步是等待2个已启动的goroutine,而不是同步对处理程序记录器的访问(这会导致数据争用)。如果您修复了数据争用,测试将正常运行并结束(等待2个启动的测试goroutine完成)。

您有一个
处理程序
实例
h
,该处理程序在每次请求时都会重新分配相同的
日志
字段。通过并发请求获得竞争非常简单。是的,但我想知道如何以编程方式确认这一点,而无需太多推理。如果你尝试并发写入,go race检测器会捕捉到这一点。如果您的测试没有以正确的方式执行此代码,您可能永远也检测不到它。尽管我以并发方式使用iiuc。那我还能怎么测试。。。以正确的方式?以编程方式检测竞争条件类似于解决停止问题,无法静态地保证可以达到特定的状态。因此,没有编程方式确定它是否具有竞争条件?哇,太棒了,谢谢。我已将你的答案添加到
http://127.0.0.1:33007/
http://127.0.0.1:33007/about
==================
WARNING: DATA RACE
Write at 0x00c000098030 by goroutine 17:
  play.(*handler).loggingMiddleware.func1()
      /home/icza/tmp/gows/src/play/main.go:16 +0x1ce
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1964 +0x51
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2741 +0xc4
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1847 +0x80a

Previous write at 0x00c000098030 by goroutine 16:
  play.(*handler).loggingMiddleware.func1()
      /home/icza/tmp/gows/src/play/main.go:16 +0x1ce
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1964 +0x51
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2741 +0xc4
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1847 +0x80a

Goroutine 17 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/go/src/net/http/server.go:2851 +0x4c5
  net/http/httptest.(*Server).goServe.func1()
      /usr/local/go/src/net/http/httptest/server.go:280 +0xac

Goroutine 16 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/go/src/net/http/server.go:2851 +0x4c5
  net/http/httptest.(*Server).goServe.func1()
      /usr/local/go/src/net/http/httptest/server.go:280 +0xac
==================
2019/01/06 14:58:50  info index                     method=GET requestURI=/
2019/01/06 14:58:50  info about                     method=GET requestURI=/about
--- FAIL: TestRace (0.00s)
    testing.go:771: race detected during execution of test
FAIL
exit status 1
FAIL    play    0.011s