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

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

Golang fatal error: concurrent map read and map write

问题

我正在使用Go语言编写Minecraft服务器,当服务器承受2000多个连接时,我遇到了以下崩溃问题:

  1. fatal error: concurrent map read and map write/root/work/src/github.com/user/imoobler/limbo.go:78 +0x351
  2. created by main.main /root/work/src/github.com/user/imoobler/limbo.go:33 +0x368

我的代码如下:

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. "bufio"
  6. "time"
  7. "math/rand"
  8. "fmt"
  9. )
  10. var (
  11. connCounter = 0
  12. )
  13. func main() {
  14. InitConfig()
  15. InitPackets()
  16. port := int(config["port"].(float64))
  17. ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. log.Println("Server launched on port", port)
  22. go KeepAlive()
  23. for {
  24. conn, err := ln.Accept()
  25. if err != nil {
  26. log.Print(err)
  27. } else {
  28. connCounter+=1
  29. go HandleConnection(conn, connCounter)
  30. }
  31. }
  32. }
  33. func KeepAlive() {
  34. r := rand.New(rand.NewSource(15768735131534))
  35. keepalive := &PacketPlayKeepAlive{
  36. id: 0,
  37. }
  38. for {
  39. for _, player := range players {
  40. if player.state == PLAY {
  41. id := int(r.Uint32())
  42. keepalive.id = id
  43. player.keepalive = id
  44. player.WritePacket(keepalive)
  45. }
  46. }
  47. time.Sleep(20000000000)
  48. }
  49. }
  50. func HandleConnection(conn net.Conn, id int) {
  51. log.Printf("%s connected.", conn.RemoteAddr().String())
  52. player := &Player {
  53. id: id,
  54. conn: conn,
  55. state: HANDSHAKING,
  56. protocol: V1_10,
  57. io: &ConnReadWrite{
  58. rdr: bufio.NewReader(conn),
  59. wtr: bufio.NewWriter(conn),
  60. },
  61. inaddr: InAddr{
  62. "",
  63. 0,
  64. },
  65. name: "",
  66. uuid: "d979912c-bb24-4f23-a6ac-c32985a1e5d3",
  67. keepalive: 0,
  68. }
  69. for {
  70. packet, err := player.ReadPacket()
  71. if err != nil {
  72. break
  73. }
  74. CallEvent("packetReceived", packet)
  75. }
  76. player.unregister()
  77. conn.Close()
  78. log.Printf("%s disconnected.", conn.RemoteAddr().String())
  79. }

目前服务器只是一个"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:

  1. package main
  2. import (
  3. &quot;log&quot;
  4. &quot;net&quot;
  5. &quot;bufio&quot;
  6. &quot;time&quot;
  7. &quot;math/rand&quot;
  8. &quot;fmt&quot;
  9. )
  10. var (
  11. connCounter = 0
  12. )
  13. func main() {
  14. InitConfig()
  15. InitPackets()
  16. port := int(config[&quot;port&quot;].(float64))
  17. ln, err := net.Listen(&quot;tcp&quot;, fmt.Sprintf(&quot;:%d&quot;, port))
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. log.Println(&quot;Server launched on port&quot;, port)
  22. go KeepAlive()
  23. for {
  24. conn, err := ln.Accept()
  25. if err != nil {
  26. log.Print(err)
  27. } else {
  28. connCounter+=1
  29. go HandleConnection(conn, connCounter)
  30. }
  31. }
  32. }
  33. func KeepAlive() {
  34. r := rand.New(rand.NewSource(15768735131534))
  35. keepalive := &amp;PacketPlayKeepAlive{
  36. id: 0,
  37. }
  38. for {
  39. for _, player := range players {
  40. if player.state == PLAY {
  41. id := int(r.Uint32())
  42. keepalive.id = id
  43. player.keepalive = id
  44. player.WritePacket(keepalive)
  45. }
  46. }
  47. time.Sleep(20000000000)
  48. }
  49. }
  50. func HandleConnection(conn net.Conn, id int) {
  51. log.Printf(&quot;%s connected.&quot;, conn.RemoteAddr().String())
  52. player := &amp;Player {
  53. id: id,
  54. conn: conn,
  55. state: HANDSHAKING,
  56. protocol: V1_10,
  57. io: &amp;ConnReadWrite{
  58. rdr: bufio.NewReader(conn),
  59. wtr: bufio.NewWriter(conn),
  60. },
  61. inaddr: InAddr{
  62. &quot;&quot;,
  63. 0,
  64. },
  65. name: &quot;&quot;,
  66. uuid: &quot;d979912c-bb24-4f23-a6ac-c32985a1e5d3&quot;,
  67. keepalive: 0,
  68. }
  69. for {
  70. packet, err := player.ReadPacket()
  71. if err != nil {
  72. break
  73. }
  74. CallEvent(&quot;packetReceived&quot;, packet)
  75. }
  76. player.unregister()
  77. conn.Close()
  78. log.Printf(&quot;%s disconnected.&quot;, conn.RemoteAddr().String())
  79. }

For now server is only "limbo".

答案1

得分: 92

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

sync.RWMutex

使用sync.RWMutex{}来控制对map的访问。如果你只进行单个读写操作,而不是对map进行循环操作,可以选择这个选项。参考RWMutex

下面是一个通过someMapMutexsomeMap进行访问控制的示例:

  1. var (
  2. someMap = map[string]string{}
  3. someMapMutex = sync.RWMutex{}
  4. )
  5. go func() {
  6. someMapMutex.Lock()
  7. someMap["key"] = "value"
  8. someMapMutex.Unlock()
  9. }()
  10. someMapMutex.RLock()
  11. v, ok := someMap["key"]
  12. someMapMutex.RUnlock()
  13. if !ok {
  14. fmt.Println("key missing")
  15. return
  16. }
  17. fmt.Println(v)

syncmap.Map

使用syncmap.Map{}代替普通的map。这个map已经处理了竞态问题,但根据你的使用情况可能会更慢。syncmap.Map{}的主要优势在于循环操作。参考syncmap

  1. var (
  2. someMap = syncmap.Map{}
  3. )
  4. go func() {
  5. someMap.Store("key", "value")
  6. }()
  7. v, ok := someMap.Load("key")
  8. if !ok {
  9. fmt.Println("key missing")
  10. return
  11. }
  12. fmt.Println(v)
  13. // 使用syncmap,可以简单地循环遍历所有的键,而不需要在整个循环过程中锁定整个map
  14. someMap.Range(func(key, value interface{}) bool {
  15. // 将value转换为正确的格式
  16. val, ok := value.(string)
  17. if !ok {
  18. // 这将中断迭代
  19. return false
  20. }
  21. // 对键/值进行操作
  22. fmt.Println(key, val)
  23. // 这将继续迭代
  24. return true
  25. })

一般建议

你应该使用-race选项测试你的服务器,并消除它报告的所有竞态条件。这样,你可以更容易地在错误发生之前消除此类错误。

  1. 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:

  1. var (
  2. someMap = map[string]string{}
  3. someMapMutex = sync.RWMutex{}
  4. )
  5. go func() {
  6. someMapMutex.Lock()
  7. someMap[&quot;key&quot;] = &quot;value&quot;
  8. someMapMutex.Unlock()
  9. }()
  10. someMapMutex.RLock()
  11. v, ok := someMap[&quot;key&quot;]
  12. someMapMutex.RUnlock()
  13. if !ok {
  14. fmt.Println(&quot;key missing&quot;)
  15. return
  16. }
  17. 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

  1. var (
  2. someMap = syncmap.Map{}
  3. )
  4. go func() {
  5. someMap.Store(&quot;key&quot;, &quot;value&quot;)
  6. }()
  7. v, ok := someMap.Load(&quot;key&quot;)
  8. if !ok {
  9. fmt.Println(&quot;key missing&quot;)
  10. return
  11. }
  12. fmt.Println(v)
  13. // with syncmap, looping over all keys is simple without locking the whole map for the entire loop
  14. someMap.Range(func(key, value interface{}) bool {
  15. // cast value to correct format
  16. val, ok := value.(string)
  17. if !ok {
  18. // this will break iteration
  19. return false
  20. }
  21. // do something with key/value
  22. fmt.Println(key, val)
  23. // this will continue iterating
  24. return true
  25. })

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.

  1. 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:

确定