golang-event在以太坊中的使用

姜家志發表於2018-03-16

go-ethereum中go-event庫的使用

github.com/ethereum/go…包實現了一個事件釋出訂閱的庫,使用介面主要是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團隊 喻建寫作,轉載無需授權。

相關文章