英文:
Golang fatal error: concurrent map read and map write
问题
我正在使用Go语言编写Minecraft服务器,当服务器承受2000多个连接时,我遇到了以下崩溃问题:
fatal error: concurrent map read and map write/root/work/src/github.com/user/imoobler/limbo.go:78 +0x351
created by main.main /root/work/src/github.com/user/imoobler/limbo.go:33 +0x368
我的代码如下:
package main
import (
"log"
"net"
"bufio"
"time"
"math/rand"
"fmt"
)
var (
connCounter = 0
)
func main() {
InitConfig()
InitPackets()
port := int(config["port"].(float64))
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatal(err)
}
log.Println("Server launched on port", port)
go KeepAlive()
for {
conn, err := ln.Accept()
if err != nil {
log.Print(err)
} else {
connCounter+=1
go HandleConnection(conn, connCounter)
}
}
}
func KeepAlive() {
r := rand.New(rand.NewSource(15768735131534))
keepalive := &PacketPlayKeepAlive{
id: 0,
}
for {
for _, player := range players {
if player.state == PLAY {
id := int(r.Uint32())
keepalive.id = id
player.keepalive = id
player.WritePacket(keepalive)
}
}
time.Sleep(20000000000)
}
}
func HandleConnection(conn net.Conn, id int) {
log.Printf("%s connected.", conn.RemoteAddr().String())
player := &Player {
id: id,
conn: conn,
state: HANDSHAKING,
protocol: V1_10,
io: &ConnReadWrite{
rdr: bufio.NewReader(conn),
wtr: bufio.NewWriter(conn),
},
inaddr: InAddr{
"",
0,
},
name: "",
uuid: "d979912c-bb24-4f23-a6ac-c32985a1e5d3",
keepalive: 0,
}
for {
packet, err := player.ReadPacket()
if err != nil {
break
}
CallEvent("packetReceived", packet)
}
player.unregister()
conn.Close()
log.Printf("%s disconnected.", conn.RemoteAddr().String())
}
目前服务器只是一个"limbo"状态。
英文:
I'm writing minecraft server in Go, when server is being stressed by 2000+ connections I get this crash:
<blockquote>
fatal error: concurrent map read and map write/root/work/src/github.com/user/imoobler/limbo.go:78 +0x351
created by main.main /root/work/src/github.com/user/imoobler/limbo.go:33 +0x368
</blockquote>
My code:
package main
import (
"log"
"net"
"bufio"
"time"
"math/rand"
"fmt"
)
var (
connCounter = 0
)
func main() {
InitConfig()
InitPackets()
port := int(config["port"].(float64))
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatal(err)
}
log.Println("Server launched on port", port)
go KeepAlive()
for {
conn, err := ln.Accept()
if err != nil {
log.Print(err)
} else {
connCounter+=1
go HandleConnection(conn, connCounter)
}
}
}
func KeepAlive() {
r := rand.New(rand.NewSource(15768735131534))
keepalive := &PacketPlayKeepAlive{
id: 0,
}
for {
for _, player := range players {
if player.state == PLAY {
id := int(r.Uint32())
keepalive.id = id
player.keepalive = id
player.WritePacket(keepalive)
}
}
time.Sleep(20000000000)
}
}
func HandleConnection(conn net.Conn, id int) {
log.Printf("%s connected.", conn.RemoteAddr().String())
player := &Player {
id: id,
conn: conn,
state: HANDSHAKING,
protocol: V1_10,
io: &ConnReadWrite{
rdr: bufio.NewReader(conn),
wtr: bufio.NewWriter(conn),
},
inaddr: InAddr{
"",
0,
},
name: "",
uuid: "d979912c-bb24-4f23-a6ac-c32985a1e5d3",
keepalive: 0,
}
for {
packet, err := player.ReadPacket()
if err != nil {
break
}
CallEvent("packetReceived", packet)
}
player.unregister()
conn.Close()
log.Printf("%s disconnected.", conn.RemoteAddr().String())
}
For now server is only "limbo".
答案1
得分: 92
一般来说(没有访问错误发生的代码),你有几个选项。以下是其中两个选项:
sync.RWMutex
使用sync.RWMutex{}
来控制对map的访问。如果你只进行单个读写操作,而不是对map进行循环操作,可以选择这个选项。参考RWMutex。
下面是一个通过someMapMutex
对someMap
进行访问控制的示例:
var (
someMap = map[string]string{}
someMapMutex = sync.RWMutex{}
)
go func() {
someMapMutex.Lock()
someMap["key"] = "value"
someMapMutex.Unlock()
}()
someMapMutex.RLock()
v, ok := someMap["key"]
someMapMutex.RUnlock()
if !ok {
fmt.Println("key missing")
return
}
fmt.Println(v)
syncmap.Map
使用syncmap.Map{}
代替普通的map
。这个map已经处理了竞态问题,但根据你的使用情况可能会更慢。syncmap.Map{}
的主要优势在于循环操作。参考syncmap。
var (
someMap = syncmap.Map{}
)
go func() {
someMap.Store("key", "value")
}()
v, ok := someMap.Load("key")
if !ok {
fmt.Println("key missing")
return
}
fmt.Println(v)
// 使用syncmap,可以简单地循环遍历所有的键,而不需要在整个循环过程中锁定整个map
someMap.Range(func(key, value interface{}) bool {
// 将value转换为正确的格式
val, ok := value.(string)
if !ok {
// 这将中断迭代
return false
}
// 对键/值进行操作
fmt.Println(key, val)
// 这将继续迭代
return true
})
一般建议
你应该使用-race
选项测试你的服务器,并消除它报告的所有竞态条件。这样,你可以更容易地在错误发生之前消除此类错误。
go run -race server.go
参考golang竞态检测器。
英文:
Generally speaking (without having access to the code where the error occurs) you have a few options. Here are two of them:
sync.RWMutex
Control access to the map with sync.RWMutex{}
. Use this option if you have single reads and writes, not loops over the map. See RWMutex
Here a sample with access control to someMap
via someMapMutex
:
var (
someMap = map[string]string{}
someMapMutex = sync.RWMutex{}
)
go func() {
someMapMutex.Lock()
someMap["key"] = "value"
someMapMutex.Unlock()
}()
someMapMutex.RLock()
v, ok := someMap["key"]
someMapMutex.RUnlock()
if !ok {
fmt.Println("key missing")
return
}
fmt.Println(v)
syncmap.Map
Use a syncmap.Map{}
instead of a normal map
. This map is already taking care of race issues but may be slower depending on your usage. syncmap.Map{}
s main advantage lies with for loops. See syncmap
var (
someMap = syncmap.Map{}
)
go func() {
someMap.Store("key", "value")
}()
v, ok := someMap.Load("key")
if !ok {
fmt.Println("key missing")
return
}
fmt.Println(v)
// with syncmap, looping over all keys is simple without locking the whole map for the entire loop
someMap.Range(func(key, value interface{}) bool {
// cast value to correct format
val, ok := value.(string)
if !ok {
// this will break iteration
return false
}
// do something with key/value
fmt.Println(key, val)
// this will continue iterating
return true
})
General Advice
You should test your server with -race
option and then eliminate all the race conditions it throws. That way you can easier eliminate such errors before they occur.
go run -race server.go
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论