我有这个注册表结构,它有一个注销客户端的通道。基本上从地图中删除客户端指针并关闭客户端发送通道。为什么这个测试会一直失败
func (r *SocketRegistry) run() {
for {
select {
case client := <-r.Register:
r.clients[client.id] = client
case client := <-r.UnRegister:
delete(r.clients, client.id)
close(client.send)
case payload := <-r.Broadcast:
var regPayload RegistryPayload
json.Unmarshal(payload, ®Payload)
client := r.clients[regPayload.ClientID]
select {
case client.send <- payload:
default:
close(client.send)
delete(r.clients, client.id)
}
}
}
}
// GetClient按id
返回客户端指针func (r *SocketRegistry) GetClient(id string) (*Client, bool) {
if client, ok := r.clients[id]; ok {
return client, ok
}
return &Client{}, false
}
这是测试
func TestRegisterClient(t *testing.T) {
registry := Registry()
go registry.run()
defer registry.stop()
var client Client
client.id = "PROPS"
client.send = make(chan []byte, 256)
registry.Register <- &client
c, _ := registry.GetClient(client.id)
if client.id != c.id {
t.Errorf("Expected client with id: %v got: %v", client.id, c.id)
}
registry.UnRegister <- &client
c, ok := registry.GetClient(client.id)
if ok {
t.Errorf("Expected false got ok: %v and client id: %v got: %v", ok, client.id, c.id)
}
}
就像地图永远不会删除密钥一样。如果我添加一些日志语句,那么它会删除密钥,这让我觉得这可能是goroutines的时间问题
答案 0 :(得分:3)
有一场比赛。在run()
被调用之前,无法保证delete(r.clients, client.id)
执行registry.GetClient(client.id)
。
race detector检测并报告问题。
像这样实现GetClient:
// add this field to Registry
get chan getRequest
struct getRequest struct {
ch chan *Client
id string
}
func (r *SocketRegistry) GetClient(id string) (*Client, bool) {
ch := make(chan *Client)
r.get <- getRequest{id, ch}
c := <- ch
if c == nil {
return &Client{}, false
}
return c, true
}
func (r *SocketRegistry) run() {
for {
select {
case gr := <-r.get:
gr.ch <- r.clients[id]
case client := <-r.Register:
... as before
}
}
我使用互斥锁代替通道和goroutine来解决这个问题:
func (r *SocketRegistry) register(c *Client) {
r.mu.Lock()
defer r.mu.Unlock()
r.clients[c.id] = c
}
func (r *SocketRegistry) unregister(c *Client) {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.clients, c.id)
close(c.send)
}
func (r *SocketRegister) get(id string) (*Client, bool) {
r.mu.Lock()
defer r.mu.Unlock()
c, ok := r.clients[id]
return c, ok
}
func (r *SocketRegistry) send(id string, data []byte) {
r.mu.Lock()
defer r.mu.Unlock()
c := r.clients[id]
select {
case c.send <- data:
default:
close(c.send)
delete(r.clients, c.id)
}
}
Goroutines很棒,但它们并不总是最适合某项工作的工具。