代码:github.com/ethereum/go-ethereum
版本:96157a897be2032be5fdb87f947fbe5df8a53bd4
event包实现了以太坊中的事件发布订阅模式,这个包中主要有两个对象,一个TypeMux
,一个Feed
,其中TypeMux
已经被标记为Deprecated,推荐使用Feed
。
TypeMux
分析 (推荐使用Feed
,但是还有地方使用)TypeMux
结构体
type TypeMux struct {
mutex sync.RWMutex
subm map[reflect.Type][]*TypeMuxSubscription //保存对应的订阅事件
stopped bool //是否停止
}
TypeMuxSubscription
结构体
type TypeMuxSubscription struct {
mux *TypeMux
created time.Time //创建时间
closeMu sync.Mutex
closing chan struct{
} //当在关闭得时候
closed bool
// these two are the same channel. they are stored separately so
// postC can be set to nil without affecting the return value of
// Chan.
postMu sync.RWMutex
readC <-chan *TypeMuxEvent
postC chan<- *TypeMuxEvent
}
TypeMuxEvent
事件数据结构体
type TypeMuxEvent struct {
Time time.Time
Data interface{
} //对应的事件得结构体
}
每个事件我们都需要先调用*TypeMux
得Subscribe
方法进行订阅,并返回订阅句柄(*TypeMuxSubscription
),句柄可以用Chan
方法获取到订阅通道(readC
)
Subscribe
方法如下:
func (mux *TypeMux) Subscribe(types ...interface{
}) *TypeMuxSubscription {
sub := newsub(mux) //这里构造TypeMuxSubscription类型
mux.mutex.Lock() //加锁,防止并发问题
defer mux.mutex.Unlock()
if mux.stopped {
//如果发布订阅已经停止了,就不能再添加了。
// set the status to closed so that calling Unsubscribe after this
// call will short circuit.
sub.closed = true
close(sub.postC)
} else {
if mux.subm == nil {
//如果还没有初始化过,先初始化,注意键名是reflect.Type类型
mux.subm = make(map[reflect.Type][]*TypeMuxSubscription)
}
for _, t := range types {
rtyp := reflect.TypeOf(t)
oldsubs := mux.subm[rtyp]
if find(oldsubs, sub) != -1 {
//如果添加过,就不需要再添加了,panic
panic(fmt.Sprintf("event: duplicate type %s in Subscribe", rtyp))
}
subs := make([]*TypeMuxSubscription, len(oldsubs)+1)//构造新的slice,长度为原来的+1
copy(subs, oldsubs) //拷贝
subs[len(oldsubs)] = sub //赋值slice
mux.subm[rtyp] = subs //赋值map
}
}
return sub //最后返回TypeMuxSubscription类型
}
newsub
方法(c是没有缓冲得通道):
func newsub(mux *TypeMux) *TypeMuxSubscription {
c := make(chan *TypeMuxEvent) //构造通道,没有缓冲得通道
return &TypeMuxSubscription{
mux: mux,
created: time.Now(), //创建时间
readC: c, //这里readC和postC是同一个通道,只不过一个出一个进
postC: c,
closing: make(chan struct{
}),
}
}
发布方可以调用Post
方法进行事件发布
Post
方法
func (mux *TypeMux) Post(ev interface{
}) error {
event := &TypeMuxEvent{
//创建内部事件
Time: time.Now(),
Data: ev,
}
rtyp := reflect.TypeOf(ev) //reflect类型
mux.mutex.RLock() //加锁防止并发读写
if mux.stopped {
//如果都已经停止了,就别再发布了
mux.mutex.RUnlock()
return ErrMuxClosed
}
subs := mux.subm[rtyp] //从map中取出来对应的订阅者
mux.mutex.RUnlock()
for _, sub := range subs {
sub.deliver(event) //处理订阅事件
}
return nil
}
从上面我们可以看到最终订阅事件都是在deliver
方法里面进行处理
deliver
方法
func (s *TypeMuxSubscription) deliver(event *TypeMuxEvent) {
// Short circuit delivery if stale event
if s.created.After(event.Time) {
//如果订阅句柄得创建时间比事件创建得时间还晚,明显有问题,返回
return
}
// Otherwise deliver the event
s.postMu.RLock()
defer s.postMu.RUnlock()
select {
case s.postC <- event: //往postC里面写入事件,上面我们说了readC和postC是同一个通道,意味着,我们往postC里面写入数据,readC就能收到对应的事件
case <-s.closing: //关闭通道
}
}
订阅方收到事件(以miner里面的update
方法为例),可以看到这边持有对应的readC
通道,当往postC
发送信息的时候,由于通道得特性,readC
就会收到信息。
func (miner *Miner) update() {
events := miner.mux.Subscribe(downloader.StartEvent{
}, downloader.DoneEvent{
}, downloader.FailedEvent{
}) //订阅事件,这里
defer func() {
if !events.Closed() {
events.Unsubscribe() //如果返回的时候没关闭,取消订阅并关闭通道
}
}()
shouldStart := false
canStart := true
dlEventCh := events.Chan() //取到对应的订阅通道 (`readC`)
for {
select {
case ev := <-dlEventCh: //传来了数据,第二步postC传过来的
if ev == nil {
//取出事件为nil,说明通道已被关闭
// Unsubscription done, stop listening
dlEventCh = nil
continue
}
switch ev.Data.(type) {
case downloader.StartEvent:
wasMining := miner.Mining()
miner.worker.stop()
canStart = false
if wasMining {
// Resume mining after sync was finished
shouldStart = true
log.Info("Mining aborted due to sync")
}
case downloader.FailedEvent:
canStart = true
if shouldStart {
miner.SetEtherbase(miner.coinbase)
miner.worker.start()
}
case downloader.DoneEvent:
canStart = true
if shouldStart {
miner.SetEtherbase(miner.coinbase)
miner.worker.start()
}
// Stop reacting to downloader events
events.Unsubscribe() //取消订阅事件
}
case addr := <-miner.startCh:
miner.SetEtherbase(addr)
if canStart {
miner.worker.start()
}
shouldStart = true
case <-miner.stopCh:
shouldStart = false
miner.worker.stop()
case <-miner.exitCh:
miner.worker.close()
return
}
}
}
我们在上面一步看到了Unsubscribe
方法,用它来进行事件得取消订阅处理。
Unsubscribe
方法
func (s *TypeMuxSubscription) Unsubscribe() {
s.mux.del(s) //删除对应slice中的订阅对象
s.closewait() //关闭通道等,做了一些收尾工作
}
del
方法
func (mux *TypeMux) del(s *TypeMuxSubscription) {
mux.mutex.Lock()
defer mux.mutex.Unlock()
for typ, subs := range mux.subm {
//重复的找,直到所有的订阅事件都被删除,上面miner update方法就一个订阅句柄对应了三个事件。
if pos := find(subs, s); pos >= 0 {
if len(subs) == 1 {
delete(mux.subm, typ)
} else {
mux.subm[typ] = posdelete(subs, pos)
}
}
}
}
从上面得分析我们可以得出,由于newsub
中构造得channel是无缓冲得,那么发送事件就是阻塞得,这点就是官方引入Feed
类型的原因。
Feed
分析Feed
结构体
type Feed struct {
once sync.Once // ensures that init only runs once 确保只初始化一次
sendLock chan struct{
} // sendLock has a one-element buffer and is empty when held.It protects sendCases. 缓冲,保护
removeSub chan interface{
} // interrupts Send 退出
sendCases caseList // the active set of select cases used by Send selectCase对象数组,用来发送
// The inbox holds newly subscribed channels until they are added to sendCases.
mu sync.Mutex
inbox caseList //新订阅得,但是还没被添加到sendCases得
etype reflect.Type //reflect类型,防止添加了不一样的类型
}
caseList
slice
type caseList []reflect.SelectCase
Subscription
接口
type Subscription interface {
Err() <-chan error // returns the error channel
Unsubscribe() // cancels sending of events, closing the error channel
}
feedSub
结构体
type feedSub struct {
feed *Feed //Feed结构体
channel reflect.Value //通道
errOnce sync.Once //关闭,只执行一次
err chan error
}
在外部调用Feed
的Subscribe
方法,进行订阅,注意传入得channel,它是核心。
Subscribe
方法:
func (f *Feed) Subscribe(channel interface{
}) Subscription {
f.once.Do(f.init) //进行初始化,一个feed变量只进行一次
chanval := reflect.ValueOf(channel) //获取到channel得reflect value
chantyp := chanval.Type() //获取到channel得reflect type
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 {
//对类型进行判断,不符合要求的进行panic,这里不能为 单向接收通道类型
panic(errBadChannel)
}
//构造一个结构体
sub := &feedSub{
feed: f, channel: chanval, err: make(chan error, 1)}
f.mu.Lock()
defer f.mu.Unlock()
if !f.typecheck(chantyp.Elem()) {
//判断类型是否相等
panic(feedTypeError{
op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)})
}
// Add the select case to the inbox.
// The next Send will add it to f.sendCases.
//先将cas方法去到inbox,在下一次发送得时候,会加入到发送列表
cas := reflect.SelectCase{
Dir: reflect.SelectSend, Chan: chanval}
f.inbox = append(f.inbox, cas)
return sub
}
发送方调用Send
方法进行事件发布(发布是往订阅得时候传入得channel里面写入数据)
Send
方法
// Send delivers to all subscribed channels simultaneously.
// It returns the number of subscribers that the value was sent to.
func (f *Feed) Send(value interface{
}) (nsent int) {
rvalue := reflect.ValueOf(value) //取出reflect值
f.once.Do(f.init) //在上面订阅得时候初始化过,个人觉得这里加上是为了保险,防止未初始化
<-f.sendLock //保证串行发送
// Add new cases from the inbox after taking the send lock.
f.mu.Lock()
f.sendCases = append(f.sendCases, f.inbox...) //将inbox里面的加进来
f.inbox = nil //indox清空
if !f.typecheck(rvalue.Type()) {
//类型检查
f.sendLock <- struct{
}{
} //写回通道值,下次可以用
f.mu.Unlock()
panic(feedTypeError{
op: "Send", got: rvalue.Type(), want: f.etype})
}
f.mu.Unlock()
// Set the sent value on all channels.
//这里从第一个开始取(因为sendCases第一个值,是一个单向接收通道类型,不能往里面发送,所以过滤掉)
for i := firstSubSendCase; i < len(f.sendCases); i++ {
f.sendCases[i].Send = rvalue //将要发送得值写入到selectCase里面
}
// Send until all channels except removeSub have been chosen. 'cases' tracks a prefix
// of sendCases. When a send succeeds, the corresponding case moves to the end of
// 'cases' and it shrinks by one element.
cases := f.sendCases //取出cases
for {
// Fast path: try sending without blocking before adding to the select set.
// This should usually succeed if subscribers are fast enough and have free
// buffer space.
//先尝试发送,如果没有阻塞,应该就会成功;
for i := firstSubSendCase; i < len(cases); i++ {
if cases[i].Chan.TrySend(rvalue) {
//尝试发送
nsent++
cases = cases.deactivate(i) //将最后一个和当前i得值交换,并丢弃最后一个元素
i--
}
}
//如果长度为一,说明只剩下单向接收通道了。就可以直接退出了。
if len(cases) == firstSubSendCase {
break
}
// Select on all the receivers, waiting for them to unblock.
chosen, recv, _ := reflect.Select(cases)
if chosen == 0 /* <-f.removeSub */ {
//如果选中了单向接收通道,就将传过来的值从sendCases中删除
index := f.sendCases.find(recv.Interface())
f.sendCases = f.sendCases.delete(index) //找到对应的index,将对应的元素从sendCases中删除
if index >= 0 && index < len(cases) {
// Shrink 'cases' too because the removed case was still active.
cases = f.sendCases[:len(cases)-1] //收缩cases
}
} else {
cases = cases.deactivate(chosen)//将最后一个和当前chosen得值交换,并丢弃最后一个元素
nsent++
}
}
// Forget about the sent value and hand off the send lock.
for i := firstSubSendCase; i < len(f.sendCases); i++ {
f.sendCases[i].Send = reflect.Value{
} //发送完成,将所有发送成功的selectCase值置为默认值
}
f.sendLock <- struct{
}{
} //往通道写值,等待下一次发送
return nsent //返回发送成功的个数
}
订阅方收到事件,我们订阅得时候传入得channel,这个时候就会收到数据。比如eth/protocols/snap/sync.go
里面的Sync
方法
Sync
方法
func (s *Syncer) Sync(root common.Hash, cancel chan struct{
}) error {
// Move the trie root from any previous value, revert stateless markers for
// any peers and initialize the syncer if it was not yet run
s.lock.Lock()
.....
// Keep scheduling sync tasks
peerJoin := make(chan string, 16)
peerJoinSub := s.peerJoin.Subscribe(peerJoin) //订阅,传入通道
defer peerJoinSub.Unsubscribe() //取消订阅
peerDrop := make(chan string, 16)
peerDropSub := s.peerDrop.Subscribe(peerDrop) //订阅,传入通道
defer peerDropSub.Unsubscribe() //取消订阅
for {
// Remove all completed tasks and terminate sync if everything's done
.....
// Wait for something to happen
select {
case <-s.update:
// Something happened (new peer, delivery, timeout), recheck tasks
case <-peerJoin: //收到数据,进行处理
// A new peer joined, try to schedule it new tasks
case id := <-peerDrop://收到数据,进行处理
s.revertRequests(id)
case <-cancel:
return nil
....
}
// Report stats if something meaningful happened
s.report(false)
}
}
调用方调用Unsubscribe
函数进行取消订阅。如上面一步中所示。
Unsubscribe
函数
func (sub *feedSub) Unsubscribe() {
sub.errOnce.Do(func() {
//只进行一次
sub.feed.remove(sub)
close(sub.err)
})
}
从上面可以看出,主要调用了Feed
的remove
方法
remove
方法
func (f *Feed) remove(sub *feedSub) {
// Delete from inbox first, which covers channels
// that have not been added to f.sendCases yet.
ch := sub.channel.Interface() //取出对应的通道值
f.mu.Lock()
index := f.inbox.find(ch) //看ch在indox里面没,在里面直接删除,就可以返回了。
if index != -1 {
f.inbox = f.inbox.delete(index)
f.mu.Unlock()
return
}
f.mu.Unlock()
select {
case f.removeSub <- ch: //正在发送.removeSub通道写入,
// Send will remove the channel from f.sendCases.
case <-f.sendLock: //没有正在发送得
// No Send is in progress, delete the channel now that we have the send lock.
f.sendCases = f.sendCases.delete(f.sendCases.find(ch)) //直接找到删除
f.sendLock <- struct{
}{
} //还给别人,方便下一个用
}
}
Feed
利用反射来实现了发布订阅模式,他跟TypeMux
得最大区别就是Feed
是非阻塞式得。它会先尝试进行发送,不行的话在进行阻塞得发送。共同点都是通过channel来进行数据得传递。
SubscriptionScope
结构体
type SubscriptionScope struct {
mu sync.Mutex
subs map[*scopeSub]struct{
}
closed bool
}
type scopeSub struct {
sc *SubscriptionScope
s Subscription
}
通过Feed
构造Subscription
,在调用SubscriptionScope
的Track
方法,进行了二次构造
Track
方法
func (sc *SubscriptionScope) Track(s Subscription) Subscription {
sc.mu.Lock()
defer sc.mu.Unlock()
if sc.closed {
return nil
}
if sc.subs == nil {
sc.subs = make(map[*scopeSub]struct{
})
}
ss := &scopeSub{
sc, s} //构造scopeSub结构体
sc.subs[ss] = struct{
}{
}
return ss
}
取消订阅Unsubscribe
func (s *scopeSub) Unsubscribe() {
s.s.Unsubscribe() //基础的取消订阅
s.sc.mu.Lock()
defer s.sc.mu.Unlock()
delete(s.sc.subs, s) //从中删除
}
结构体
type funcSub struct {
unsub chan struct{
}
err chan error
mu sync.Mutex
unsubscribed bool
}
构造Subscription
,主要逻辑由传入得producer实现
func NewSubscription(producer func(<-chan struct{
}) error) Subscription {
s := &funcSub{
unsub: make(chan struct{
}), err: make(chan error, 1)}
go func() {
defer close(s.err)
err := producer(s.unsub)
s.mu.Lock()
defer s.mu.Unlock()
if !s.unsubscribed {
if err != nil {
s.err <- err
}
s.unsubscribed = true
}
}()
return s
}
取消订阅Unsubscribe
func (s *funcSub) Unsubscribe() {
s.mu.Lock()
if s.unsubscribed {
s.mu.Unlock()
return
}
s.unsubscribed = true
close(s.unsub)
s.mu.Unlock()
// Wait for producer shutdown.
<-s.err //这里等待producer函数退出
}