编写一个简单的EventBus
先放github地址
用go写一个Pub/Sub比Java简单多了,因为go有chan这机制。
总线(Bus)
管理所有专题(topic)和订阅该专题的用户。以map形式存储。
这里加一把表级锁。
type Bus struct {
subNode map[string]*node
rw sync.RWMutex
}
节点(node)
node内管理着订阅同一专题的用户序列。
这里加了一把序列锁,在Bus的表级锁被举起时,node的锁不会使用,这样能减小锁粒度,提高并发度。
// node contains a slice of subs that subscribe same topic
type node struct {
subs []Sub
// Note node's rw will not be used when bus's rw is helded.
rw sync.RWMutex
}
用户(Sub)
Sub内有一个chan成员变量,每次有消息被发送到chan内时,由于chan的机制,Sub就能及时收到被推送的消息。
type Sub struct {
out chan interface{}
}
订阅
把sub添加到bus中map便利的topic对应的序列中。
注意这里用到了两种锁的上锁解锁时机。
func (b *Bus) Subscribe(topic string, sub Sub) {
b.rw.Lock()
if n, ok := b.subNode[topic]; ok {
// found the node
b.rw.Unlock()
n.rw.Lock()
defer n.rw.Unlock()
n.subs = append(n.subs, sub)
} else {
defer b.rw.Unlock()
n := NewNode()
b.subNode[topic] = &n
}
}
发布
逻辑就是遍历用户序列,把消息发送到sub的chan中
func (b *Bus) Publish(topic string, msg interface{}) error {
b.rw.Lock()
if n, ok := b.subNode[topic]; ok {
// found the node
b.rw.Unlock()
n.rw.RLock()
defer n.rw.RUnlock()
// got the subs list and publish msg
go func(subs []Sub, msg interface{}) {
for _, sub := range subs {
sub.receive(msg)
}
}(n.subs, msg)
// successed return null
return nil
} else {
// topic not exist
return errors.New("topic not exist")
}
}
使用
见仓库内的test文件