私はGoでminecraftサーバーを書いています。サーバーが2000以上の接続によってストレスを受けているとき、このクラッシュが発生します。
致命的なエラー:同時マップ読み取りとマップ書き込み/root/work/src/github.com/user/imoobler/limbo.go:78 + 0x351 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())
}
現時点では、サーバーは「リンボ」のみです。
一般的に言えば(エラーが発生するコードにアクセスできない場合)、いくつかのオプションがあります。以下はそのうちの2つです。
sync.RWMutex{}
で地図へのアクセスを制御します。このオプションは、マップをループするのではなく、単一の読み取りと書き込みがある場合に使用します。 RWMutex を参照
以下は、someMap
を介した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)
通常のmap
の代わりにsyncmap.Map{}
を使用してください。このマップはすでにレースの問題を処理していますが、使用状況によっては遅くなる場合があります。 syncmap.Map{}
sの主な利点はforループにあります。 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
})
-race
オプションを使用してサーバーをテストし、サーバーがスローするすべての競合状態を排除する必要があります。そうすれば、そのようなエラーが発生する前に簡単に排除できます。
go run -race server.go
golang競合検出器 を参照してください