以太坊之event包--发布订阅模式详解

代码:github.com/ethereum/go-ethereum
版本:96157a897be2032be5fdb87f947fbe5df8a53bd4

一、总结

event包实现了以太坊中的事件发布订阅模式,这个包中主要有两个对象,一个TypeMux,一个Feed,其中TypeMux已经被标记为Deprecated,推荐使用Feed

二、源码分析

1、TypeMux分析 (推荐使用Feed,但是还有地方使用)

a、主要结构体

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{
   
   } //对应的事件得结构体
}

b、流程梳理

1、订阅事件

每个事件我们都需要先调用*TypeMuxSubscribe方法进行订阅,并返回订阅句柄(*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{
   
   }),
   }
}
2、发布事件

发布方可以调用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: //关闭通道
   }
}
3、收到事件

订阅方收到事件(以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
      }
   }
}
4、取消订阅

我们在上面一步看到了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)
         }
      }
   }
}
5、总结

从上面得分析我们可以得出,由于newsub中构造得channel是无缓冲得,那么发送事件就是阻塞得,这点就是官方引入Feed类型的原因。

2、Feed分析

a、主要结构体

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
}

b、流程梳理

1、订阅事件

在外部调用FeedSubscribe方法,进行订阅,注意传入得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
}
2、发送事件

发送方调用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 //返回发送成功的个数
}
3、收到事件

订阅方收到事件,我们订阅得时候传入得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)
   }
}
4、取消订阅

调用方调用Unsubscribe函数进行取消订阅。如上面一步中所示。

Unsubscribe函数

func (sub *feedSub) Unsubscribe() {
   
   
   sub.errOnce.Do(func() {
   
    //只进行一次
      sub.feed.remove(sub)
      close(sub.err)
   })
}

从上面可以看出,主要调用了Feedremove方法

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{
   
   }{
   
   } //还给别人,方便下一个用
   }
}
5、总结

Feed利用反射来实现了发布订阅模式,他跟TypeMux得最大区别就是Feed是非阻塞式得。它会先尝试进行发送,不行的话在进行阻塞得发送。共同点都是通过channel来进行数据得传递。

3、其他方式

a、封装SubscriptionScope

结构体

type SubscriptionScope struct {
   
   
   mu     sync.Mutex
   subs   map[*scopeSub]struct{
   
   }
   closed bool
}

type scopeSub struct {
   
   
   sc *SubscriptionScope
   s  Subscription
}

通过Feed构造Subscription,在调用SubscriptionScopeTrack方法,进行了二次构造

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) //从中删除
}

b、封装匿名函数

结构体

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函数退出
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章