Golang在http handlefunc中更新map。

huangapple go评论68阅读模式
英文:

Golang map update on http handlefunc

问题

我对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) {
		// 这个map的添加操作是安全的吗?还是我需要使用线程锁(互斥锁等)?
		clientsData[request.RemoteAddr] = ...
	})
	http.ListenAndServe("127.0.0.10:8090", nil)
}
英文:

I have a question about map in go language.
I want to handle the clients (http) and save some of their information using map (key (client IP) value pair) ...
http handle each http client using a new thread, so I think changing (add, delete, edit) the map data will be unsafe ... Is my action safe ?

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)
}

答案1

得分: 2

简化示例中的代码,可以构建一个简单的示例来展示它是不安全的。

使用以下简单程序:

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)

这个示例展示了并发访问时的不安全性。

英文:

Simplying the sample in https://eli.thegreenplace.net/2019/on-concurrency-in-go-http-servers/ allow to build a simple example that show it is not safe.

Using a simple program like :

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)
}

And stimulating using :

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

Produce exception like :

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)

答案2

得分: 0

如果只是读取,是安全的。

如果需要写入,需要一种“线程安全”的方式来进行。

第一个想法是使用sync.Mutex来保护对映射的访问。

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

第二种方法是使用读/写互斥锁来控制读取和写入。许多goroutine可以读取,但每次只能写入一个。

还有其他来自sync和sync/atomic包的选项。

还有一种额外的方法要考虑:如果您只需要写入此映射,可以考虑使用带缓冲的通道将键/值结构发送到单个goroutine中(负责将其存储到映射中)。

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

您甚至可以使用通道通过回调函数进行安全读取/写入,但这是另外一个故事。

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

英文:

If you only read, it is safe.

If you need to write, you need some way to do it “thread-safe”

The first idea is to use a sync.Mutex to protect the access to the map

However this may became a bottleneck, since you may have several requests in parallel but only one can write each time. We are talking about nanoseconds…

A second approach can be use a read/write mutex to control reading and writting. Many goroutines can read, but only one can write per time.

There are other options from package sync and sync/atomic.

There is one extra approach to consider: if you need only write into this map you can consider use a buffered channel to send a structure of key/value to be consumed in a single goroutine (responsible for store it into the map)

As you can see, this approach have many
Advantages IF it makes sense to your application.

You can even use channels to safe read / write via callbacks but this is another story.

If you have a doubt, write unit tests and use the race condition detector

huangapple
  • 本文由 发表于 2022年3月30日 02:32:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/71667160.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定