我有一个注册表结构,它有一个取消注册客户端的通道。基本上从映射中删除客户端指针并关闭客户端发送通道。为什么这个测试总是失败
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 的计时问题
有一场比赛。不能保证run()
在调用registry.GetClient(client.id)
之前执行delete(r.clients, client.id)
。
比赛检测器检测并报告问题。
像这样实现 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很棒,但它们并不总是给定工作的最佳工具。