【问题标题】:Should I RLock map before range?我应该在范围之前 RLock 地图吗?
【发布时间】:2016-09-21 02:28:55
【问题描述】:

如果多个 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...
}

【问题讨论】:

标签: dictionary go concurrency


【解决方案1】:

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

因此,如果您需要从不同的 go-routines 访问地图,则必须采用某种形式的访问编排,否则“不受控制的地图访问可能会使程序崩溃”(请参阅​​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
}

编辑:

我的立场是正确的;根据Damian Gryski,地图对于并发读取是安全的。每次迭代时 map 顺序发生变化的原因是“为 map 迭代顺序选择的随机种子,它是 goroutine 迭代的本地”(他的另一个 tweet)。这一事实不影响第一次编辑和建议的解决方案。

【讨论】:

  • 有第二个代码示例,在这种情况下是否正确?
  • 否;因为您正在另一个 go-routine 中删除该项目,该项目可能在 notifyAll 退出并且 defer mu.RUnlock() 行当然被执行后开始。换句话说,将您的地图锁定在 notifyAll 中与您在内部 go-routine 中所做的事情无关。
  • 我正在使用 mu.Lock() 删除项目,而不仅仅是简单的删除。你能告诉我那必须怎么做吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-22
  • 2015-07-12
  • 1970-01-01
  • 1970-01-01
  • 2014-05-02
相关资源
最近更新 更多