以太坊原始碼分析(38)event原始碼分析

尹成發表於2018-05-14
event包實現了同一個程式內部的事件釋出和訂閱模式。

## event.go

目前這部分程式碼被標記為Deprecated,告知使用者使用Feed這個物件。 不過在程式碼中任然有使用。 而且這部分的程式碼也不多。 就簡單介紹一下。

資料結構
TypeMux是主要的使用。 subm記錄了所有的訂閱者。 可以看到每中型別都可以有很多的訂閱者。
    
    // TypeMuxEvent is a time-tagged notification pushed to subscribers.
    type TypeMuxEvent struct {
        Time time.Time
        Data interface{}
    }
    
    // A TypeMux dispatches events to registered receivers. Receivers can be
    // registered to handle events of certain type. Any operation
    // called after mux is stopped will return ErrMuxClosed.
    //
    // The zero value is ready to use.
    //
    // Deprecated: use Feed
    type TypeMux struct {
        mutex sync.RWMutex
        subm map[reflect.Type][]*TypeMuxSubscription
        stopped bool
    }


建立一個訂閱,可以同時訂閱多種型別。

    // Subscribe creates a subscription for events of the given types. The
    // subscription's channel is closed when it is unsubscribed
    // or the mux is closed.
    func (mux *TypeMux) Subscribe(types ...interface{}) *TypeMuxSubscription {
        sub := newsub(mux)
        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 {
                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(fmt.Sprintf("event: duplicate type %s in Subscribe", rtyp))
                }
                subs := make([]*TypeMuxSubscription, len(oldsubs)+1)
                copy(subs, oldsubs)
                subs[len(oldsubs)] = sub
                mux.subm[rtyp] = subs
            }
        }
        return sub
    }

    // TypeMuxSubscription is a subscription established through TypeMux.
    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 和 postC 其實是同一個channel。 不過一個是從channel讀 一個只從channel寫
        // 單方向的channel
        readC <-chan *TypeMuxEvent
        postC chan<- *TypeMuxEvent
    }
    
    func newsub(mux *TypeMux) *TypeMuxSubscription {
        c := make(chan *TypeMuxEvent)
        return &TypeMuxSubscription{
            mux: mux,
            created: time.Now(),
            readC: c,
            postC: c,
            closing: make(chan struct{}),
        }
    }

釋出一個event到TypeMux上面,這個時候所有訂閱了這個型別的都會收到這個訊息。

    // Post sends an event to all receivers registered for the given type.
    // It returns ErrMuxClosed if the mux has been stopped.
    func (mux *TypeMux) Post(ev interface{}) error {
        event := &TypeMuxEvent{
            Time: time.Now(),
            Data: ev,
        }
        rtyp := reflect.TypeOf(ev)
        mux.mutex.RLock()
        if mux.stopped {
            mux.mutex.RUnlock()
            return ErrMuxClosed
        }
        subs := mux.subm[rtyp]
        mux.mutex.RUnlock()
        for _, sub := range subs {
            // 阻塞式的投遞。
            sub.deliver(event)
        }
        return nil
    }

    
    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:
        case <-s.closing:
        }
    }


## feed.go
目前主要使用的物件。取代了前面說的event.go內部的TypeMux

feed資料結構

    // Feed implements one-to-many subscriptions where the carrier of events is a channel.
    // Values sent to a Feed are delivered to all subscribed channels simultaneously.
    // Feed 實現了 1對多的訂閱模式,使用了channel來傳遞事件。 傳送給Feed的值會同時被傳遞給所有訂閱的channel。
    // Feeds can only be used with a single type. The type is determined by the first Send or
    // Subscribe operation. Subsequent calls to these methods panic if the type does not
    // match.
    // Feed只能被單個型別使用。這個和之前的event不同,event可以使用多個型別。 型別被第一個Send呼叫或者是Subscribe呼叫決定。 後續的呼叫如果型別和其不一致會panic
    // The zero value is ready to use.
    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
    
        // The inbox holds newly subscribed channels until they are added to sendCases.
        mu sync.Mutex
        inbox caseList
        etype reflect.Type
        closed bool
    }

初始化 初始化會被once來保護保證只會被執行一次。

    func (f *Feed) init() {
        f.removeSub = make(chan interface{})
        f.sendLock = make(chan struct{}, 1)
        f.sendLock <- struct{}{}
        f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
    }

訂閱,訂閱投遞了一個channel。 相對與event的不同。event的訂閱是傳入了需要訂閱的型別,然後channel是在event的訂閱程式碼裡面構建然後返回的。 這種直接投遞channel的模式可能會更加靈活。
然後根據傳入的channel生成了SelectCase。放入inbox。
    
    // Subscribe adds a channel to the feed. Future sends will be delivered on the channel
    // until the subscription is canceled. All channels added must have the same element type.
    //
    // The channel should have ample buffer space to avoid blocking other subscribers.
    // Slow subscribers are not dropped.
    func (f *Feed) Subscribe(channel interface{}) Subscription {
        f.once.Do(f.init)
    
        chanval := reflect.ValueOf(channel)
        chantyp := chanval.Type()
        if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 { // 如果型別不是channel。 或者是channel的方向不能傳送資料。那麼錯誤退出。
            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 := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval}
        f.inbox = append(f.inbox, cas)
        return sub
    }


Send方法,feed的Send方法不是遍歷所有的channel然後阻塞方式的傳送。這樣可能導致慢的客戶端影響快的客戶端。 而是使用反射的方式使用SelectCase。 首先呼叫非阻塞方式的TrySend來嘗試傳送。這樣如果沒有慢的客戶端。資料會直接全部傳送完成。 如果TrySend部分客戶端失敗。 那麼後續在迴圈Select的方式傳送。 我猜測這也是feed會取代event的原因。


    // 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) {
        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...)
        f.inbox = nil
        f.mu.Unlock()
    
        // Set the sent value on all channels.
        rvalue := reflect.ValueOf(value)
        if !f.typecheck(rvalue.Type()) {
            f.sendLock <- struct{}{}
            panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
        }
        for i := firstSubSendCase; i < len(f.sendCases); i++ {
            f.sendCases[i].Send = rvalue
        }
    
        // Send until all channels except removeSub have been chosen.
        cases := f.sendCases
        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--
                }
            }
            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 */ {
                index := f.sendCases.find(recv.Interface())
                f.sendCases = f.sendCases.delete(index)
                if index >= 0 && index < len(cases) {
                    cases = f.sendCases[:len(cases)-1]
                }
            } else {
                cases = cases.deactivate(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{}
        }
        f.sendLock <- struct{}{}
        return nsent
    }
    




網址:http://www.qukuailianxueyuan.io/



欲領取造幣技術與全套虛擬機器資料

區塊鏈技術交流QQ群:756146052  備註:CSDN

尹成學院微信:備註:CSDN



相關文章