【问题标题】:Golang fatal error: concurrent map read and map writeGolang致命错误:并发地图读取和地图写入
【发布时间】:2018-01-17 01:01:52
【问题描述】:

我正在用 Go 编写我的世界服务器,当服务器受到 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())
}

目前服务器只是“边缘”。

【问题讨论】:

    标签: dictionary go


    【解决方案1】:

    一般来说(无法访问发生错误的代码)您有几个选择。以下是其中两个:

    sync.RWMutex

    使用sync.RWMutex{} 控制对地图的访问。如果您有单次读取和写入,而不是在地图上循环,请使用此选项。见RWMutex

    这里是通过someMapMutexsomeMap 进行访问控制的示例:

    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{}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 race detector

    【讨论】:

    • github.com/TyphoonMC/TyphoonLimbo 有我使用的完整代码
    • 好的,很抱歉我的错误,它可能不是地图错误。 /root/work/src/github.com/user/imoobler/utils.go:33 我已经在上面发布了我的源代码。
    • 你能运行-race检测吗?
    • 当然,pastebin.com/4sX56En3 使用 -race 参数后完全错误
    • 在我看来,行号已更改为github.com/TyphoonMC/TyphoonLimbo 上的代码。 limbo.go:44 似乎是正确的并指向此代码for _, player := range players {。我假设players 映射由多个 go 例程访问。您需要控制对此map 的访问(通过Mutex/RWMutex)或按照我的回答中的建议使用syncmap
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-31
    • 2014-12-05
    • 1970-01-01
    • 1970-01-01
    • 2016-12-21
    相关资源
    最近更新 更多