這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
關於erlang的有限狀態機器,參考erlang四大behaviour之二-gen_fsm這篇文章。
有限狀態機器可以用下面這個公式來表達
State(S) x Event(E) -> Actions(A), State(S')
這兩天正好空閑,就用golang實現了一下,話不多說,直接上代碼
package utilimport ("errors""reflect""sync""time""unicode""unicode/utf8""util/log")var typeOfError = reflect.TypeOf((*error)(nil)).Elem()type FSM struct {sync.MutexStopReason stringrcvr reflect.Value // receiver of methods for the servicetyp reflect.Type // type of the receivermethod map[string]reflect.Methodevent chan Eventquit chan intstate stringstopped bool}type Event struct {event stringparam interface{}timeout int}func (fsm *FSM) IsStopped() bool {fsm.Lock()defer fsm.Unlock()return fsm.stopped}func (fsm *FSM) SendEvent(event string, param interface{}) {fsm.Lock()defer fsm.Unlock()if fsm.stopped {return}fsm.event <- Event{event, param, 0}}func (fsm *FSM) Init(start string) error {if _, ok := fsm.method[start]; !ok {return errors.New("not found state")}fsm.state = startgo func() {for {select {case e := <-fsm.event:go fsm.CallState(e)case <-fsm.quit:goto close}}close:close(fsm.event)close(fsm.quit)}()return nil}func (fsm *FSM) CallState(e Event) {fsm.Lock()defer fsm.Unlock()if function, ok := fsm.method[fsm.state]; ok {returnValues := function.Func.Call([]reflect.Value{fsm.rcvr, reflect.ValueOf(e.event), reflect.ValueOf(e.param), reflect.ValueOf(e.timeout)})nextstate := returnValues[0].String()timeout := returnValues[1].Int()errInter := returnValues[2].Interface()errmsg := ""if errInter != nil {errmsg = errInter.(error).Error()}if nextstate == "stop" {fsm.Stop(errmsg)fsm.quit <- 1return}if errmsg != "" {log.LogError(errmsg)}fsm.state = nextstateif timeout > 0 {go fsm.DelayCall(time.Duration(timeout))}}}func (fsm *FSM) DelayCall(timeout time.Duration) {select {case <-time.After(timeout * time.Millisecond):fsm.event <- Event{"timeout", 0, int(timeout)}}}func (fsm *FSM) Stop(message string) {fsm.StopReason = messagefsm.stopped = true}func (fsm *FSM) Close() {fsm.Lock()defer fsm.Unlock()if fsm.stopped {return}fsm.quit <- 1}func NewFSM(fsm interface{}) *FSM {f := &FSM{typ: reflect.TypeOf(fsm), rcvr: reflect.ValueOf(fsm), event: make(chan Event), quit: make(chan int)}f.method = suitableMethods(f.typ, true)return f}func isExported(name string) bool {rune, _ := utf8.DecodeRuneInString(name)return unicode.IsUpper(rune)}func suitableMethods(typ reflect.Type, reportErr bool) map[string]reflect.Method {methods := make(map[string]reflect.Method)for m := 0; m < typ.NumMethod(); m++ {method := typ.Method(m)mtype := method.Typemname := method.Nameif !isExported(mname) {continue}// Method needs four ins: receiver, string, interface{}, int.if mtype.NumIn() != 4 {if reportErr {log.LogError("method", mname, "has wrong number of ins:", mtype.NumIn())}continue}// First arg must be a string.if mtype.In(1).Kind() != reflect.String {if reportErr {log.LogError("method", mname, "arg1 type not a string:", mtype.In(1).Kind())}continue}// Second arg must be a interface.if mtype.In(2).Kind() != reflect.Interface {if reportErr {log.LogError("method", mname, "arg2 type not a interface:", mtype.In(2).Kind())}continue}// Third arg must be a int.if mtype.In(3).Kind() != reflect.Int {if reportErr {log.LogError("method", mname, "arg3 type not a int:", mtype.In(3).Kind())}continue}// Method needs three out.if mtype.NumOut() != 3 {if reportErr {log.LogError("method", mname, "has wrong number of outs:", mtype.NumOut())}continue}if mtype.Out(0).Kind() != reflect.String {if reportErr {log.LogError("method", mname, "out1 type not a string:", mtype.Out(0).Kind())}continue}if mtype.Out(1).Kind() != reflect.Int {if reportErr {log.LogError("method", mname, "out1 type not a int:", mtype.Out(1).Kind())}continue}if mtype.Out(2) != typeOfError {if reportErr {log.LogError("method", mname, "out3 type not a error:", mtype.In(2).Kind())}continue}methods[mname] = method}return methods}
下面就是使用方法:
type GoFSM struct {}func (f *GoFSM) State1(event string, param interface{}, t int) (nextstate string, timeout int, err error) {log.LogMessage(event, param.(int))return "State2", 100, nil //如果timeout大於0,則在timeout毫秒後,自動調用下一個狀態,下一個狀態的event為timeout}func (f *GoFSM) State2(event string, param interface{}, t int) (nextstate string, timeout int, err error) {log.LogMessage(event)return "stop", 0, errors.New("stop ok") //nextstate=stop則停止狀態機器,err為停止原因}func main() {f := util.NewFSM(&GoFSM{})f.Init("State1") //初始狀態f.SendEvent("Do", 1) //發送事件time.Sleep(time.Second * 1)}
所有的狀態回呼函數,必須以大寫字母開頭,原型必須是
func(event string, param interface{}, t int) (nextstate string, timeout int, err error)
event是事件名,param為事件的參數,t>0表示這是一個延時事件。傳回值:nextstate為新的狀態,必須和狀態回呼函數同名,如果為"stop"則表示沒有後續的狀態,狀態機器停止。timeout>0表示延時回調,將在timeout時間後,產生一個timeout事件。