代碼: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、訂閱事件
每個事件我們都需要先調用*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{
}),
}
}
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、訂閱事件
在外部調用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
}
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)
})
}
從上面可以看出,主要調用了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{
}{
} //還給別人,方便下一個用
}
}
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
,在調用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) //從中刪除
}
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函數退出
}