Should I RLock map before range?

我的梦境 提交于 2019-12-24 09:38:32

问题


Is it safe to range map without locking if multiple goroutines will run notifyAll func? Actually in a range I need to sometimes remove entries from a map.

var mu sync.RWMutex

func (self *Server) notifyAll(event *Event)
  ch := make(chan int, 64)
  num := 0
  for k, v := range self.connections {
    num++
    ch <- num

    go func(int k, conn *Conn) {
      err := conn.sendMessage(event)
      <-ch
      if err != nil {
        self.removeConn(k)
      }
    }(k, v)
  }
}

func (self *Server) removeConn(int k) {
  mu.Lock()
  defer mu.Unlock()
  delete(self.connections, k)
}

// Somewhere in another goroutine
func (self *Server) addConn(conn *Conn, int k) {
  mu.Lock()
  defer mu.Unlock()
  self.connections[k] = conn
}

Or I must RLock map before range?

func (self *Server) notifyAll(event *Event)
  mu.RLock()
  defer mu.RUnlock()
  // Skipped previous body...
}

回答1:


You must lock the map to prevent concurrent reads in notifyAll with the write in removeConn.




回答2:


Short answer: maps are not concurrent-safe (one can still say thread-safe) in Go.

So, if you need to access a map from different go-routines, you must employ some form of access orchestration, otherwise "uncontrolled map access can crash the program" (see this).

Edit:

This is another implementation (without considering housekeeping concerns - timeouts, quit, log, etc) which ignores the mutex all-together and uses a more Goish approach (this is just for demonstrating this approach which helps us to clear access orchestration concerns - might be right or not for your case):

type Server struct {
    connections map[*Conn]struct{}

    _removeConn, _addConn chan *Conn
    _notifyAll            chan *Event
}

func NewServer() *Server {
    s := new(Server)
    s.connections = make(map[*Conn]struct{})
    s._addConn = make(chan *Conn)
    s._removeConn = make(chan *Conn, 1)
    s._notifyAll = make(chan *Event)
    go s.agent()
    return s
}

func (s *Server) agent() {
    for {
        select {
        case c := <-s._addConn:
            s.connections[c] = struct{}{}
        case c := <-s._removeConn:
            delete(s.connections, c)
        case e := <-s._notifyAll:
            for c := range s.connections {
                closure := c
                go func() {
                    err := closure.sendMessage(e)
                    if err != nil {
                        s._removeConn <- closure
                    }
                }()
            }
        }
    }
}

func (s *Server) removeConn(c *Conn) {
    s._removeConn <- c
}

func (s *Server) addConn(c *Conn) {
    s._addConn <- c
}

Edit:

I stand corrected; according to Damian Gryski maps are safe for concurrent reads. The reason that the map order changes on each iteration is "the random seed chosen for map iteration order, which is local to the goroutine iterating" (another tweet of him). This fact does not affect the first edit and suggested solution.



来源:https://stackoverflow.com/questions/37404025/should-i-rlock-map-before-range

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!