Golang致命错误:并发地读取和写入映射。

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

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 (
	&quot;log&quot;
	&quot;net&quot;
	&quot;bufio&quot;
	&quot;time&quot;
	&quot;math/rand&quot;
	&quot;fmt&quot;
)

var (
	connCounter = 0
)

func main() {
	InitConfig()
	InitPackets()

	port := int(config[&quot;port&quot;].(float64))
	ln, err := net.Listen(&quot;tcp&quot;, fmt.Sprintf(&quot;:%d&quot;, port))
	if err != nil {
		log.Fatal(err)
	}
	log.Println(&quot;Server launched on port&quot;, 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 := &amp;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(&quot;%s connected.&quot;, conn.RemoteAddr().String())

	player := &amp;Player {
		id: id,
		conn: conn,
		state: HANDSHAKING,
		protocol: V1_10,
		io: &amp;ConnReadWrite{
			rdr: bufio.NewReader(conn),
			wtr: bufio.NewWriter(conn),
		},
		inaddr: InAddr{
			&quot;&quot;,
			0,
		},
		name: &quot;&quot;,
		uuid: &quot;d979912c-bb24-4f23-a6ac-c32985a1e5d3&quot;,
		keepalive: 0,
	}

	for {
		packet, err := player.ReadPacket()
		if err != nil {
			break
		}

		CallEvent(&quot;packetReceived&quot;, packet)
	}

	player.unregister()
	conn.Close()
	log.Printf(&quot;%s disconnected.&quot;, conn.RemoteAddr().String())
}

For now server is only "limbo".

答案1

得分: 92

一般来说(没有访问错误发生的代码),你有几个选项。以下是其中两个选项:

sync.RWMutex

使用sync.RWMutex{}来控制对map的访问。如果你只进行单个读写操作,而不是对map进行循环操作,可以选择这个选项。参考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。这个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[&quot;key&quot;] = &quot;value&quot;
	someMapMutex.Unlock()
}()

someMapMutex.RLock()
v, ok := someMap[&quot;key&quot;]
someMapMutex.RUnlock()
if !ok {
	fmt.Println(&quot;key missing&quot;)
	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(&quot;key&quot;, &quot;value&quot;)
}()

v, ok := someMap.Load(&quot;key&quot;)
if !ok {
	fmt.Println(&quot;key missing&quot;)
	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

See golang race detector

huangapple
  • 本文由 发表于 2017年8月9日 16:36:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/45585589.html
匿名

发表评论

匿名网友

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

确定