golang-event在以太坊中的使用

go-ethereum中go-event库的使用

github.com/ethereum/go-ethereum/event包实现了一个事件发布订阅的库,使用接口主要是event.Feed 类型,以前还有event.TypeMux 类型,看代码注释,说过时了,目前主要使用Feed 类型。

package main

import (
    "fmt"
    "sync"
    "github.com/ethereum/go-ethereum/event"
)

func main() {
    type someEvent struct{ I int }

    var feed event.Feed
    var wg sync.WaitGroup

    ch := make(chan someEvent)
    sub := feed.Subscribe(ch)

    wg.Add(1)
    go func() {
        defer wg.Done()
        for event := range ch {
            fmt.Printf("Received: %#v\n", event.I)
        }
        sub.Unsubscribe()
        fmt.Println("done")
    }()

    feed.Send(someEvent{5})
    feed.Send(someEvent{10})
    feed.Send(someEvent{7})
    feed.Send(someEvent{14})
    close(ch)

    wg.Wait()
}

通过调用event.Feed 类型的Subscrible方法订阅事件通知,需要使用者提前指定接收事件的channel,Subscribe返回Subscription对象,是一个接口类型:

type Subscription interface {
            Err() <-chan error      // returns the error channel
            Unsubscribe()           // cancels sending of events, closing the error channel
}

Err() 返回获取error 的channel,调用Unsubscribe()取消事件订阅。事件的发布者调用 Send() 方法,发送事件。
可以使用同一个channel实例,多次调用Feed 的Subscrible()方法:

package main

import (
    "fmt"
    "sync"

    "github.com/ethereum/go-ethereum/event"
)

func main() {

    var (
        feed   event.Feed
        recv   sync.WaitGroup
        sender sync.WaitGroup
    )

    ch := make(chan int)
    feed.Subscribe(ch)
    feed.Subscribe(ch)
    feed.Subscribe(ch)

    expectSends := func(value, n int) {
        defer sender.Done()
        if nsent := feed.Send(value); nsent != n {
            fmt.Printf("send delivered %d times, want %d\n", nsent, n)
        }
    }
    expectRecv := func(wantValue, n int) {
        defer recv.Done()
        for v := range ch {
            if v != wantValue {
                fmt.Printf("received %d, want %d\n", v, wantValue)
            } else {
                fmt.Printf("recv v = %d\n", v)
            }
        }
    }

    sender.Add(3)
    for i := 0; i < 3; i++ {
        go expectSends(1, 3)
    }
    go func() {
        sender.Wait()
        close(ch)
    }()
    recv.Add(1)
    go expectRecv(1, 3)
    recv.Wait()
}

这个例子中, 有三个订阅者, 有三个发送者, 每个发送者发送三次1, 同一个channel ch 里面被推送了9个1.
ethereum event 库还提供了一些高级别的方便接口, 比如event.NewSubscription函数,接收一个函数类型,作为数据的生产者, producer本身在后台一个单独的goroutine内执行, 后台goroutine往用户的channel 发送数据:

package main

import (
    "fmt"

    "github.com/ethereum/go-ethereum/event"
)

func main() {
    ch := make(chan int)
    sub := event.NewSubscription(func(quit <-chan struct{}) error {
        for i := 0; i < 10; i++ {
            select {
            case ch <- i:
            case <-quit:
                fmt.Println("unsubscribed")
                return nil
            }
        }
        return nil
    })

    for i := range ch {
        fmt.Println(i)
        if i == 4 {
            sub.Unsubscribe()
            break
        }
    }
}

库也提供了event.SubscriptionScope类型用于追踪多个订阅者,提供集中的取消订阅功能:

package main

import (
    "fmt"
    "sync"

    "github.com/ethereum/go-ethereum/event"
)

// This example demonstrates how SubscriptionScope can be used to control the lifetime of
// subscriptions.
//
// Our example program consists of two servers, each of which performs a calculation when
// requested. The servers also allow subscribing to results of all computations.
type divServer struct{ results event.Feed }
type mulServer struct{ results event.Feed }

func (s *divServer) do(a, b int) int {
    r := a / b
    s.results.Send(r)
    return r
}

func (s *mulServer) do(a, b int) int {
    r := a * b
    s.results.Send(r)
    return r
}

// The servers are contained in an App. The app controls the servers and exposes them
// through its API.
type App struct {
    divServer
    mulServer
    scope event.SubscriptionScope
}

func (s *App) Calc(op byte, a, b int) int {
    switch op {
    case '/':
        return s.divServer.do(a, b)
    case '*':
        return s.mulServer.do(a, b)
    default:
        panic("invalid op")
    }
}

// The app's SubscribeResults method starts sending calculation results to the given
// channel. Subscriptions created through this method are tied to the lifetime of the App
// because they are registered in the scope.
func (s *App) SubscribeResults(op byte, ch chan<- int) event.Subscription {
    switch op {
    case '/':
        return s.scope.Track(s.divServer.results.Subscribe(ch))
    case '*':
        return s.scope.Track(s.mulServer.results.Subscribe(ch))
    default:
        panic("invalid op")
    }
}

// Stop stops the App, closing all subscriptions created through SubscribeResults.
func (s *App) Stop() {
    s.scope.Close()
}

func main() {
    var (
        app  App
        wg   sync.WaitGroup
        divs = make(chan int)
        muls = make(chan int)
    )

    divsub := app.SubscribeResults('/', divs)
    mulsub := app.SubscribeResults('*', muls)
    wg.Add(1)
    go func() {
        defer wg.Done()
        defer fmt.Println("subscriber exited")
        for {
            select {
            case result := <-divs:
                fmt.Println("division happened:", result)
            case result := <-muls:
                fmt.Println("multiplication happened:", result)
            case divErr := <-divsub.Err():
                fmt.Println("divsub.Err() :", divErr)
                return
            case mulErr := <-mulsub.Err():
                fmt.Println("mulsub.Err() :", mulErr)
                return
            }
        }
    }()

    app.Calc('/', 22, 11)
    app.Calc('*', 3, 4)

    app.Stop()
    wg.Wait()
}

SubscriptionScope的Close() 方法接收Track方法的返回值 , Track 方法负责追踪订阅者。


本文由 Copernicus团队 喻建写作,转载无需授权。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容