http handlefunc上的Golang地图更新



我有一个关于go语言中map的问题。我想处理客户端(http(,并使用map(密钥(客户端IP(值对(保存它们的一些信息。。。http使用一个新线程处理每个http客户端,所以我认为更改(添加、删除、编辑(map数据是不安全的。。。我的行动安全吗?

package main
import (
"net/http"
)
func main() {
var clientsData map[string]string
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
// Is this map adding, safe or i have to use thread lock (mutex or ...) ?
clientsData[request.RemoteAddr] = ...
})
http.ListenAndServe("127.0.0.10:8090", nil)
}

在https://eli.thegreenplace.net/2019/on-concurrency-in-go-http-servers/允许构建一个简单的示例来表明它是不安全的。

使用一个简单的程序,如:

package main
import (
"net/http"
)
func main() {
counters := map[string]int{}
name := "test"
counters[name] = 0
http.HandleFunc("/test", func(w http.ResponseWriter, req *http.Request) {
counters[name]++
})
http.ListenAndServe(":8000", nil)
}

和刺激使用:

ab -n 20000 -c 200 "127.0.0.1:8000/test"

生成异常,如:

goroutine 158 [running]:
runtime.throw({0x64d95a, 0x81faa0})
/usr/local/go/src/runtime/panic.go:1198 +0x71 fp=0xc000384980 sp=0xc000384950 pc=0x4348f1
runtime.mapaccess2_faststr(0x697360, 0xc0003a4ba0, {0x644851, 0x4})
/usr/local/go/src/runtime/map_faststr.go:116 +0x3d4 fp=0xc0003849e8 sp=0xc000384980 pc=0x413d34
main.main.func1({0x69bf00, 0xc0003a4b60}, 0x0)
/home/test/gohttp/main.go:13 +0x46 fp=0xc000384a48 sp=0xc0003849e8 pc=0x5eba86
net/http.HandlerFunc.ServeHTTP(0x0, {0x69bf00, 0xc0003a4b60}, 0x0)

如果你只阅读,它是安全的。

如果你需要写作,你需要一些"线程安全"的方法

第一个想法是使用同步。Mutex保护对地图的访问

然而,这可能会成为一个瓶颈,因为您可能有多个并行请求,但每次只能写入一个。我们谈论的是纳秒…

第二种方法可以使用读/写互斥来控制读和写。许多goroutines可以阅读,但每次只有一个可以写作。

包同步和同步/原子还有其他选项。

有一种额外的方法需要考虑:如果你只需要写入这个映射,你可以考虑使用缓冲通道发送一个密钥/值的结构,以便在单个goroutine中使用(负责将其存储到映射中(

正如你所看到的,这种方法有很多优点如果它对您的应用程序有意义。

你甚至可以通过回调使用安全的读写通道,但这是另一回事。

如果您有疑问,请编写单元测试并使用竞赛条件检测器

最新更新