我应该在范围之前RLock地图吗?

时间:2016-05-24 03:44:43

标签: dictionary go concurrency

如果多个goroutine将运行notifyAll func,那么在没有锁定的情况下对范围映射是否安全?实际上在一个范围内,我有时需要从地图中删除条目。

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
}

或者我必须在射程前RLock地图?

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

2 个答案:

答案 0 :(得分:0)

您必须锁定地图,以防止在notifyAll中使用removeConn进行并发读取。

答案 1 :(得分:0)

简短回答:Go中的地图不是并发安全的(人们仍然可以说是线程安全的)。

因此,如果您需要从不同的例程中访问地图,则必须采用某种形式的访问编排,否则“不受控制的地图访问会导致程序崩溃”(请参阅​​this)。

修改

这是另一个实现(不考虑内务管理问题 - 超时,退出,日志等)忽略互斥并使用更Goish方法(这只是为了演示这种方法,这有助于我们清除访问协调问题 - 对你的情况可能是对的):

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
}

修改

我坚持认可;根据{{​​3}}映射对于并发读取是安全的。地图顺序在每次迭代中更改的原因是“为地图迭代顺序选择的随机种子,这是goroutine迭代的本地”(他的另一个Damian Gryski)。这个事实不会影响第一次编辑和建议的解决方案。