以太坊go-ethereum源码中发送事件除了用常规的通道以外,还用了封装的Feed结构来执行事件的订阅和发送。以太坊中使用了大量的Feed来处理事件。使用Feed订阅事件的步骤是:
一个feed可以订阅多个通道,当使用feed发送数据后,所有的通道都将接收到数据。下文将解读Feed的源码,在进入Feed源码解读之前我们先介绍一下go中的reflect包中的SelectCase。
对于多个通道ch1,ch2,ch3,使用传统的Select方式来监听:
package main
import (
"fmt"
"strconv"
)
func main() {
var chs1 = make(chan int)
var chs2 = make(chan float64)
var chs3 = make(chan string)
var ch4close = make(chan int)
defer close(ch4close)
go func(c chan int, ch4close chan int) {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
ch4close <- 1
}(chs1, ch4close)
go func(c chan float64, ch4close chan int) {
for i := 0; i < 5; i++ {
c <- float64(i) + 0.1
}
close(c)
ch4close <- 1
}(chs2, ch4close)
go func(c chan string, ch4close chan int) {
for i := 0; i < 5; i++ {
c <- "string:" + strconv.Itoa(i)
}
close(c)
ch4close <- 1
}(chs3, ch4close)
done := 0
finished := 0
for finished < 3 {
select {
case v, ok := <-chs1:
if ok {
done = done + 1
fmt.Println(0, v)
}
case v, ok := <-chs2:
if ok {
done = done + 1
fmt.Println(1, v)
}
case v, ok := <-chs3:
if ok {
done = done + 1
fmt.Println(2, v)
}
case _, ok := <- ch4close:
if ok {
finished = finished+1
}
}
}
fmt.Println("Done", done)
}
使用reflect的方式来监听:
package main
import (
"fmt"
"reflect"
"strconv"
)
func main() {
var chs1 = make(chan int)
var chs2 = make(chan float64)
var chs3 = make(chan string)
var ch4close = make(chan int)
defer close(ch4close)
go func(c chan int, ch4close chan int) {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
ch4close <- 1
}(chs1, ch4close)
go func(c chan float64, ch4close chan int) {
for i := 0; i < 5; i++ {
c <- float64(i) + 0.1
}
close(c)
ch4close <- 1
}(chs2, ch4close)
go func(c chan string, ch4close chan int) {
for i := 0; i < 5; i++ {
c <- "string:" + strconv.Itoa(i)
}
close(c)
ch4close <- 1
}(chs3, ch4close)
var selectCase = make([]reflect.SelectCase, 4)
selectCase[0].Dir = reflect.SelectRecv
selectCase[0].Chan = reflect.ValueOf(chs1)
selectCase[1].Dir = reflect.SelectRecv
selectCase[1].Chan = reflect.ValueOf(chs2)
selectCase[2].Dir = reflect.SelectRecv
selectCase[2].Chan = reflect.ValueOf(chs3)
selectCase[3].Dir = reflect.SelectRecv
selectCase[3].Chan = reflect.ValueOf(ch4close)
done := 0
finished := 0
for finished < len(selectCase)-1 {
chosen, recv, recvOk := reflect.Select(selectCase)
if recvOk {
done = done+1
switch chosen {
case 0:
fmt.Println(chosen, recv.Int())
case 1:
fmt.Println(chosen, recv.Float())
case 2:
fmt.Println(chosen, recv.String())
case 3:
finished = finished+1
done = done-1
// fmt.Println("finished\t", finished)
}
}
}
fmt.Println("Done", done)
}
这里构建了一个reflect.SelectCase数组selectCase,将要监听的通道添加到数组中。监听时只要使用reflect.Select(selectCase)就可以监听所有通道的消息。当通道数多的时候,用SelectCase的方式将会更简洁优雅。
Feed结构的源码在event/feed.go中。
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
}
type caseList []reflect.SelectCase
Feed结构核心的是inbox成员,它是一个SelectCase的数组,保存了该Feed订阅的所有通道。sendCase是所有活跃的通道数组。sendLock通道用来作为锁来保护sendCase。
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}}
}
这里sendLock被设置成有容量为1的缓冲通道。并且给sendLock先写入了一个值。sendCases预先加入了removeSub通道作为第一个通道。
//这个通道需要有足够的缓冲空间以避免阻塞其它订阅者。速度慢的订阅者不会被丢弃
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 {
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
}
这个函数做的事情很简单,就是根据通道ch构造一个SelectCase对象,然后将其加入到inbox数组中。这样就完成了通道的订阅。
// 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)
f.once.Do(f.init)//重新初始化,onece.Do保证只会执行一次
<-f.sendLock //读sendLock通道,若sendLock为空则会堵塞
// Add new cases from the inbox after taking the send lock.
f.mu.Lock() //访问公共变量加锁
f.sendCases = append(f.sendCases, f.inbox...)//将inbox注入到sendCase
f.inbox = nil
if !f.typecheck(rvalue.Type()) {
f.sendLock <- struct{}{} //出错了,退出前先写sendLock以免下次send操作堵塞
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
}
f.mu.Unlock()
// 给所有通道设置要发送的数据
for i := firstSubSendCase; i < len(f.sendCases); i++ {
f.sendCases[i].Send = rvalue
}
// 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
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++ {
//首先使用TrySend进行发送,这是一种非阻塞操作。当订阅者足够快时一般能够立即成功
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)//等待通道返回
//<-f.removeSub
if chosen == 0 {
index := f.sendCases.find(recv.Interface())
f.sendCases = f.sendCases.delete(index)
if index >= 0 && index < len(cases) {
// Shrink 'cases' too because the removed case was still active.
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{}{}//返回时写入sendLock,为下次发送做准备
return nsent
}
send函数使用通道的trySend方法来发送,在正常情况下能够立即发送成功,但是当接收通道堵塞的时候,则需要用Select方法这种堵塞的方式等待通道发送成功。在最后返回时,写入sendLock,为下次发送做准备。
我们看到send函数使用了sendLock通道,它是一个容量为1的通道。在send函数最开始,读出sendLock通道,如果这个时候sendLock为空,则send函数就会堵塞。所以在send函数最后,写入了sendLock通道,这样下次发送去读sendLock时就不会堵塞。看起来好像没有问题,但是理想很丰满,显示有时候会骨感。这里存在的问题就是chosen, recv, _ := reflect.Select(cases)这行代码可能会堵塞,导致for循环一值退不出,send函数发生堵塞,导致sendLock不会被写入。从而导致了死锁。下次send发送就会被堵塞。
这里使用sendLock是为了保护公共的sendCase数据,解决思路是去掉sendCase,不适用全局的sendCase,而使用局部变量。这样就不用考虑同步的问题了。改造后的send函数:
func (f *Feed) Send(value interface{}) (nsent int) {
rvalue := reflect.ValueOf(value)
f.once.Do(f.init)
//<-f.sendLock
sendCases := caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
sendCases = append(sendCases, f.inbox...)
// Set the sent value on all channels.
for i := firstSubSendCase; i < len(sendCases); i++ {
sendCases[i].Send = rvalue
}
// 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 := sendCases
//LOOP:
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)
//<-f.removeSub
if chosen == 0 {
index := f.sendCases.find(recv.Interface())
f.sendCases = f.sendCases.delete(index)
if index >= 0 && index < len(cases) {
// Shrink 'cases' too because the removed case was still active.
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
}
某次send可能会堵塞,但是不会影响下次send发送。
我们看core/blockchain.go中的发送函数PostChainEvents():
// PostChainEvents iterates over the events generated by a chain insertion and
// posts them into the event feed.
// TODO: Should not expose PostChainEvents. The chain events should be posted in WriteBlock.
func (bc *BlockChain) PostChainEvents(events []interface{}, logs []*types.Log) {
log.Info("lzj-log PostChainEvents", "events len",len(events))
// post event logs for further processing
if logs != nil {
bc.logsFeed.Send(logs)
}
for _, event := range events {
switch ev := event.(type) {
case ChainEvent:
log.Info("lzj-log send ChainEvent")
bc.chainFeed.Send(ev)
case ChainHeadEvent:
log.Info("lzj-log send ChainHeadEvent")
bc.chainHeadFeed.Send(ev)
case ChainSideEvent:
log.Info("lzj-log send ChainSideEvent")
bc.chainSideFeed.Send(ev)
}
}
}
这个函数是在for循环中先后发送了ChainEvent、ChainHeadEvent和ChainSideEvent事件。在insert函数中调用了这个 函数。但是这里有个问题,如果前一个事件发送堵塞了,后面的事件发送就不会执行。需要把Send函数放到单独的协程中去。改成这样可以防止堵塞的问题:
// PostChainEvents iterates over the events generated by a chain insertion and
// posts them into the event feed.
// TODO: Should not expose PostChainEvents. The chain events should be posted in WriteBlock.
func (bc *BlockChain) PostChainEvents(events []interface{}, logs []*types.Log) {
log.Info("lzj-log PostChainEvents", "events len",len(events))
// post event logs for further processing
if logs != nil {
bc.logsFeed.Send(logs)
}
for _, event := range events {
switch ev := event.(type) {
case ChainEvent:
log.Info("lzj-log send ChainEvent")
go bc.chainFeed.Send(ev)
case ChainHeadEvent:
log.Info("lzj-log send ChainHeadEvent")
go bc.chainHeadFeed.Send(ev)
case ChainSideEvent:
log.Info("lzj-log send ChainSideEvent")
go bc.chainSideFeed.Send(ev)
}
}
}
在go里面使用通道要发非常小心,因为很容易引起堵塞从而达不到自己期望的结果。