如果多个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...
}
答案 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)。这个事实不会影响第一次编辑和建议的解决方案。