首页 > 解决方案 > 何时将 sync.Mutex 与 net/http 和 gorilla/mux 一起使用

问题描述

据我所知,net/http 包使用 goroutines 作为处理程序。sync.Mutex为了防止函数中可能出现的错误nextId导致函数可以计算地图的旧状态,是否有必要锁定地图?

这是我的示例代码:

package main

import (
    "net/http"
    "github.com/gorilla/mux"
    "io/ioutil"
    "fmt"
)

var testData = map[int]string {
    1: "foo",
    2: "bar",
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/data", getData).Methods("GET")
    r.HandleFunc("/data", addData).Methods("POST")
    http.ListenAndServe(":3000", r)
}

func getData(writer http.ResponseWriter, request *http.Request) {
    for k, v := range testData {
        fmt.Fprintf(writer, "Key: %d\tValue: %v\n", k, v)
    }
}

func addData(writer http.ResponseWriter, request *http.Request) {
    if data, err := ioutil.ReadAll(request.Body); err == nil {
        if len(data) == 0 {
            writer.WriteHeader(http.StatusBadRequest)
            return
        }

        id := nextId()
        testData[id] = string(data)
        url := request.URL.String()
        writer.Header().Set("Location", fmt.Sprintf("%s", url))
        writer.WriteHeader(http.StatusCreated)

    } else {
        writer.WriteHeader(http.StatusBadRequest)
    }
}

func nextId() int {
    id := 1

    for k, _ := range testData {
        if k >= id {
            id = k + 1;
        }
    }

    return id
}

标签: go

解决方案


由于标准 lib 的 HTTP 服务器在其自己的 goroutine 上调用处理程序,因此您必须同步访问在处理程序之外定义的所有变量(其中一种访问是写入)。每当您使用 stdlib 的 HTTP 服务器时,您都必须这样做。无论您使用标准库的多路复用器还是 Gorilla 的多路复用器都没有关系。goroutine 启动发生在多路复用器之外(在调用多路复用器之前)。

如果不这样做(例如在您的示例中),则会发生数据竞争,您可以通过使用以下-race选项运行它来验证:

WARNING: DATA RACE
Write at 0x00c000090c30 by goroutine 21:
  runtime.mapassign_fast64()
      /usr/local/go/src/runtime/map_fast64.go:92 +0x0
  main.addData()
      /home/icza/gows/src/play/play.go:47 +0x191
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2007 +0x51
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /home/icza/gows/pkg/mod/github.com/gorilla/mux@v1.7.3/mux.go:212 +0x13e
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2802 +0xce
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1890 +0x837

Previous read at 0x00c000090c30 by goroutine 7:
  runtime.mapiternext()
      /usr/local/go/src/runtime/map.go:851 +0x0
  main.getData()
      /home/icza/gows/src/play/play.go:32 +0x194
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:2007 +0x51
...

推荐阅读