Golang致命错误:并发地图读取和地图写入 [英] Golang fatal error: concurrent map read and map write

查看:199
本文介绍了Golang致命错误:并发地图读取和地图写入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用Go编写Minecraft服务器,当服务器受到2000多个连接的压力时,我会发生此崩溃:

I'm writing minecraft server in Go, when server is being stressed by 2000+ connections I get this crash:

致命错误:并发地图读取和地图写入/root/work/src/github.com/user/imoobler/limbo.go:78 + 0x351 由main.main/root/work/src/github.com/user/imoobler/limbo.go创建:33 + 0x368
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".

For now server is only "limbo".

推荐答案

通常来说(没有访问发生错误的代码的权限),您可以选择几种方法.这是其中的两个:

Generally speaking (without having access to the code where the error occurs) you have a few options. Here are two of them:

使用sync.RWMutex{}控制对地图的访问.如果您具有单次读写操作,而不是循环映射,请使用此选项.请参见 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

这里有一个可以通过someMapMutex访问someMap的示例:

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

使用syncmap.Map{}代替普通的map.该地图已经解决了种族问题,但根据您的使用情况可能会更慢. syncmap.Map{}的主要优点在于for循环.请参见同步地图

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

一般建议

您应该使用-race选项测试服务器,然后消除它引发的所有竞争条件.这样一来,您就可以更轻松地在此类错误发生之前消除它们.

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

请参见 golang种族检测器

这篇关于Golang致命错误:并发地图读取和地图写入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆