精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Go 語(yǔ)言 fsm 源碼解讀,這一次讓你徹底學(xué)會(huì)有限狀態(tài)機(jī)

開(kāi)發(fā) 前端
本篇文章我?guī)阃暾喿x了有限狀態(tài)機(jī)的核心源碼,為你理清了 FSM 的設(shè)計(jì)思路和它提供的能力。讓你能夠知其然,也能知其所以然。

本篇文章,我將更進(jìn)一步,直接通過(guò)解讀源碼的方式,讓你深刻理解 fsm 是如何實(shí)現(xiàn)的,這一次你將徹底掌握有限狀態(tài)機(jī)。

源碼解讀

廢話不多說(shuō),我們直接上代碼。

結(jié)構(gòu)體

首先 fsm 包定義了一個(gè)結(jié)構(gòu)體 FSM 用來(lái)表示狀態(tài)機(jī)。

https://github.com/looplab/fsm/blob/main/fsm.go#L40

// FSM 是持有「當(dāng)前狀態(tài)」的狀態(tài)機(jī)。
type FSM struct {
    // FSM 當(dāng)前狀態(tài)
    current string

    // transitions 將「事件和原狀態(tài)」映射到「目標(biāo)狀態(tài)」。
    transitions map[eKey]string

    // callbacks 將「回調(diào)類型和目標(biāo)」映射到「回調(diào)函數(shù)」。
    callbacks map[cKey]Callback

    // transition 是內(nèi)部狀態(tài)轉(zhuǎn)換函數(shù),可以直接使用,也可以在異步狀態(tài)轉(zhuǎn)換時(shí)調(diào)用。
    transition func()
    // transitionerObj 用于調(diào)用 FSM 的 transition() 函數(shù)。
    transitionerObj transitioner

    // stateMu 保護(hù)對(duì)當(dāng)前狀態(tài)的訪問(wèn)。
    stateMu sync.RWMutex
    // eventMu 保護(hù)對(duì) Event() 和 Transition() 兩個(gè)函數(shù)的調(diào)用。
    eventMu sync.Mutex

    // metadata 可以用來(lái)存儲(chǔ)和加載可能跨事件使用的數(shù)據(jù)
    // 使用 SetMetadata() 和 Metadata() 方法來(lái)存儲(chǔ)和加載數(shù)據(jù)。
    metadata map[string]interface{}
    // metadataMu 保護(hù)對(duì)元數(shù)據(jù)的訪問(wèn)。
    metadataMu sync.RWMutex
}

我們知道,有限狀態(tài)機(jī)中最重要的三個(gè)特征如下:

? 狀態(tài)(state)個(gè)數(shù)是有限的。

? 任意一個(gè)時(shí)刻,只處于其中一種狀態(tài)。

? 某種條件下(觸發(fā)某種 event),會(huì)從一種狀態(tài)轉(zhuǎn)變(transition)為另一種狀態(tài)。

所以,<font style="color:rgb(33, 33, 33);">FSM</font> 結(jié)構(gòu)體中一定包含與這些特征有關(guān)的字段。

current 表示狀態(tài)機(jī)的當(dāng)前狀態(tài)。

transitions 用于記錄狀態(tài)轉(zhuǎn)換規(guī)則,即定義觸發(fā)某一事件時(shí),允許從某一種狀態(tài),轉(zhuǎn)換成另一種狀態(tài)。它是一個(gè) map 對(duì)象,其 key 為 eKey 類型:

// eKey is a struct key used for storing the transition map.
type eKey struct {
    // event is the name of the event that the keys refers to.
    event string

    // src is the source from where the event can transition.
    src string
}

eKey 類型用來(lái)記錄事件和原狀態(tài)。map 的 value 為 string 類型,用來(lái)記錄目標(biāo)狀態(tài)。

callbacks 用于記錄事件觸發(fā)時(shí)的回調(diào)函數(shù)。它也是一個(gè) map 對(duì)象,其 key 為 cKey 類型:

// cKey is a struct key used for keeping the callbacks mapped to a target.
type cKey struct {
    // target is either the name of a state or an event depending on which
    // callback type the key refers to. It can also be "" for a non-targeted
    // callback like before_event.
    target string

    // callbackType is the situation when the callback will be run.
    callbackType int
}

cKey 類型用來(lái)記錄目標(biāo)和回調(diào)類型,其中目標(biāo)可以是狀態(tài)或事件名稱,回調(diào)類型可選值如下:

const (
    // 未設(shè)置回調(diào)
    callbackNone int = iota
    // 事件觸發(fā)前執(zhí)行的回調(diào)
    callbackBeforeEvent
    // 離開(kāi)舊狀態(tài)前執(zhí)行的回調(diào)
    callbackLeaveState
    // 進(jìn)入新?tīng)顟B(tài)是執(zhí)行的回調(diào)
    callbackEnterState
    // 事件完成時(shí)執(zhí)行的回調(diào)
    callbackAfterEvent
)

回調(diào)類型決定了回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)。

map 的 value 為回調(diào)函數(shù),其聲明類型如下:

// Callback is a function type that callbacks should use. Event is the current
// event info as the callback happens.
type Callback func(context.Context, *Event)

還記得回調(diào)函數(shù)是如何注冊(cè)的嗎?

fsm.Callbacks{
    // 任一事件發(fā)生之前觸發(fā)
    "before_event": func(_ context.Context, e *fsm.Event) {
        color.HiMagenta("| before event\t | %s | %s |", e.Src, e.Dst)
    },
}

這里注冊(cè)的 before_event 回調(diào)函數(shù)簽名就是 Callback 類型。

當(dāng)然這里還使用了 fsm.Callbacks 類型來(lái)注冊(cè),想必你已經(jīng)猜到了 fsm.Callbacks 的類型:

// Callbacks is a shorthand for defining the callbacks in NewFSM.
type Callbacks map[string]Callback

接下來(lái)的 transition 和 transitionerObj 兩個(gè)屬性是用來(lái)實(shí)現(xiàn)狀態(tài)轉(zhuǎn)換的,暫且留到后續(xù)使用時(shí)再來(lái)研究。

這里還有兩個(gè)互斥鎖,分別用來(lái)保護(hù)對(duì)當(dāng)前狀態(tài)的訪問(wèn)(stateMu),和保證事件觸發(fā)時(shí)的操作并發(fā)安全(eventMu)。

最后 FSM 還提供了 metadata 和 metadataMu 兩個(gè)屬性,這倆屬性用于管理元數(shù)據(jù)信息,后文中我會(huì)演示其使用場(chǎng)景。

現(xiàn)在,我們可以總結(jié)一下 FSM 結(jié)構(gòu)體定義:

FSMFSM

接下來(lái),我將對(duì) FSM 結(jié)構(gòu)體所實(shí)現(xiàn)的方法進(jìn)行講解。

方法

我們先來(lái)看一下 FSM 結(jié)構(gòu)體都提供了哪些方法和能力:

FSMFSM

這里列出了 FSM 結(jié)構(gòu)體實(shí)現(xiàn)的所有方法,并且做了分類,你先有個(gè)感官上的認(rèn)識(shí),接下來(lái)我們依次解讀。

構(gòu)造函數(shù)

我們最先要分析的源碼,當(dāng)然是 FSM 結(jié)構(gòu)體的構(gòu)造函數(shù)了,其實(shí)現(xiàn)如下:

func NewFSM(initial string, events []EventDesc, callbacks map[string]Callback) *FSM {
    // 構(gòu)造有限狀態(tài)機(jī) FSM
    f := &FSM{
        transitionerObj: &transitionerStruct{},        // 狀態(tài)轉(zhuǎn)換器,使用默認(rèn)實(shí)現(xiàn)
        current:         initial,                      // 當(dāng)前狀態(tài)
        transitions:     make(map[eKey]string),        // 存儲(chǔ)「事件和原狀態(tài)」到「目標(biāo)狀態(tài)」的轉(zhuǎn)換規(guī)則映射
        callbacks:       make(map[cKey]Callback),      // 回調(diào)函數(shù)映射表
        metadata:        make(map[string]interface{}), // 元信息
    }

    // 構(gòu)建 f.transitions map,并且存儲(chǔ)所有的「事件」和「狀態(tài)」集合
    allEvents := make(map[string]bool) // 存儲(chǔ)所有事件的集合
    allStates := make(map[string]bool) // 存儲(chǔ)所有狀態(tài)的集合
    for _, e := range events {         // 遍歷事件列表,提取并存儲(chǔ)所有事件和狀態(tài)
        for _, src := range e.Src {
            f.transitions[eKey{e.Name, src}] = e.Dst
            allStates[src] = true
            allStates[e.Dst] = true
        }
        allEvents[e.Name] = true
    }

    // 提取「回調(diào)函數(shù)」到「事件和原狀態(tài)」的映射關(guān)系,并注冊(cè)到 callbacks
    for name, fn := range callbacks {
        var target string    // 目標(biāo):狀態(tài)/事件
        var callbackType int// 回調(diào)類型(決定了調(diào)用順序)

        // 根據(jù)回調(diào)函數(shù)名稱前綴分類
        switch {
        // 事件觸發(fā)前執(zhí)行
        case strings.HasPrefix(name, "before_"):
            target = strings.TrimPrefix(name, "before_")
            if target == "event" { // 全局事件前置鉤子(任何事件觸發(fā)都會(huì)調(diào)用,如用于日志記錄場(chǎng)景)
                target = ""http:// 將 target 置空
                callbackType = callbackBeforeEvent
            } elseif _, ok := allEvents[target]; ok { // 在特定事件前執(zhí)行
                callbackType = callbackBeforeEvent
            }
        // 離開(kāi)當(dāng)前狀態(tài)前執(zhí)行
        case strings.HasPrefix(name, "leave_"):
            target = strings.TrimPrefix(name, "leave_")
            if target == "state" { // 全局狀態(tài)離開(kāi)鉤子
                target = ""
                callbackType = callbackLeaveState
            } elseif _, ok := allStates[target]; ok { // 離開(kāi)舊狀態(tài)前執(zhí)行
                callbackType = callbackLeaveState
            }
        // 進(jìn)入新?tīng)顟B(tài)后執(zhí)行
        case strings.HasPrefix(name, "enter_"):
            target = strings.TrimPrefix(name, "enter_")
            if target == "state" { // 全局狀態(tài)進(jìn)入鉤子
                target = ""
                callbackType = callbackEnterState
            } elseif _, ok := allStates[target]; ok { // 進(jìn)入新?tīng)顟B(tài)后執(zhí)行
                callbackType = callbackEnterState
            }
        // 事件完成后執(zhí)行
        case strings.HasPrefix(name, "after_"):
            target = strings.TrimPrefix(name, "after_")
            if target == "event" { // 全局事件后置鉤子
                target = ""
                callbackType = callbackAfterEvent
            } elseif _, ok := allEvents[target]; ok { // 事件完成后執(zhí)行
                callbackType = callbackAfterEvent
            }
        // 處理未加前綴的回調(diào)(簡(jiǎn)短版本)
        default:
            target = name                       // 狀態(tài)/事件
            if _, ok := allStates[target]; ok { // 如果 target 為某個(gè)狀態(tài),則 callbackType 會(huì)置為與 enter_[target] 相同
                callbackType = callbackEnterState
            } elseif _, ok := allEvents[target]; ok { // 如果 target 為某個(gè)事件,則 callbackType 會(huì)置為與 after_[target] 相同
                callbackType = callbackAfterEvent
            }
        }

        // 記錄 callbacks map
        if callbackType != callbackNone {
            // key: callbackType(用于決定執(zhí)行順序) + target(如果是全局鉤子,則 target 為空,否則,target 為狀態(tài)/事件)
            // val: 事件觸發(fā)時(shí)需要執(zhí)行的回調(diào)函數(shù)
            f.callbacks[cKey{target, callbackType}] = fn
        }
    }

    return f
}

構(gòu)造函數(shù)內(nèi)部代碼比較多,我們可以將它的核心邏輯分為 3 塊,分別是:構(gòu)造有限狀態(tài)機(jī) FSM、記錄事件(event)和狀態(tài)(state)、注冊(cè)回調(diào)函數(shù)。

構(gòu)造有限狀態(tài)機(jī) FSM 部分的代碼比較簡(jiǎn)單:

// 構(gòu)造有限狀態(tài)機(jī) FSM
f := &FSM{
    transitionerObj: &transitionerStruct{},        // 狀態(tài)轉(zhuǎn)換器,使用默認(rèn)實(shí)現(xiàn)
    current:         initial,                      // 當(dāng)前狀態(tài)
    transitions:     make(map[eKey]string),        // 存儲(chǔ)「事件和原狀態(tài)」到「目標(biāo)狀態(tài)」的轉(zhuǎn)換規(guī)則映射
    callbacks:       make(map[cKey]Callback),      // 回調(diào)函數(shù)映射表
    metadata:        make(map[string]interface{}), // 元信息
}

使用函數(shù)參數(shù) initial 作為狀態(tài)機(jī)的當(dāng)前狀態(tài),幾個(gè) map 類型的屬性,都賦予了默認(rèn)值。

接下來(lái)的部分代碼邏輯用于記錄事件(event)和狀態(tài)(state):

// 構(gòu)建 f.transitions map,并且存儲(chǔ)所有的「事件」和「狀態(tài)」集合
allEvents := make(map[string]bool) // 存儲(chǔ)所有事件的集合
allStates := make(map[string]bool) // 存儲(chǔ)所有狀態(tài)的集合
for _, e := range events {         // 遍歷事件列表,提取并存儲(chǔ)所有事件和狀態(tài)
    for _, src := range e.Src {
        f.transitions[eKey{e.Name, src}] = e.Dst
        allStates[src] = true
        allStates[e.Dst] = true
    }
    allEvents[e.Name] = true
}

這里 allEvents 和 allStates 都是集合類型(Set),分別用于記錄所有注冊(cè)的事件和狀態(tài)。

最后這一部分代碼用來(lái)注冊(cè)回調(diào)函數(shù):

for name, fn := range callbacks {
    var target string    // 目標(biāo):狀態(tài)/事件
    var callbackType int// 回調(diào)類型(決定了調(diào)用順序)

    // 根據(jù)回調(diào)函數(shù)名稱前綴分類
    switch {
    // 事件觸發(fā)前執(zhí)行
    case strings.HasPrefix(name, "before_"):
        target = strings.TrimPrefix(name, "before_")
        if target == "event" { // 全局事件前置鉤子(任何事件觸發(fā)都會(huì)調(diào)用,如用于日志記錄場(chǎng)景)
            target = ""http:// 將 target 置空
            callbackType = callbackBeforeEvent
        } elseif _, ok := allEvents[target]; ok { // 在特定事件前執(zhí)行
            callbackType = callbackBeforeEvent
        }
    ...
    }

    // 記錄 callbacks map
    if callbackType != callbackNone {
        // key: callbackType(用于決定執(zhí)行順序) + target(如果是全局鉤子,則 target 為空,否則,target 為狀態(tài)/事件)
        // val: 事件觸發(fā)時(shí)需要執(zhí)行的回調(diào)函數(shù)
        f.callbacks[cKey{target, callbackType}] = fn
    }
}

這里遍歷了 callbacks 列表,并根據(jù)回調(diào)函數(shù)名稱前綴分類,然后注冊(cè)到 f.callbacks 屬性的 map 對(duì)象中。

NOTE:

代碼注釋中的“鉤子”就代表回調(diào)函數(shù),只不過(guò)是另一種叫法罷了。

我們?cè)賮?lái)回顧一下回調(diào)函數(shù)是如何注冊(cè)的:

fsm.Callbacks{
    "before_event": func(_ context.Context, e *fsm.Event) { ... },
}

這個(gè)參數(shù)被傳入構(gòu)造函數(shù)后,會(huì)進(jìn)入 strings.HasPrefix(name, "before_")這個(gè) case,然后if target == "event"成立,此時(shí)target將會(huì)被置空,回調(diào)類型callbackType將被賦值為callbackBeforeEvent。如果我們注冊(cè)的是before_closed回調(diào)函數(shù),則target值為closed。對(duì)于target不同處理,將決定最后回調(diào)函數(shù)的執(zhí)行順序。我們暫且不繼續(xù)深入,留個(gè)懸念,后續(xù)解讀回調(diào)函數(shù)相關(guān)的源碼,你就能白為什么了。

不過(guò),我還要特別強(qiáng)調(diào)一下 default 分支的 case:

default:
    target = name                       // 狀態(tài)/事件
    if _, ok := allStates[target]; ok { // 如果 target 為某個(gè)狀態(tài),則 callbackType 會(huì)置為與 enter_[target] 相同,即二者等價(jià)
        callbackType = callbackEnterState
    } else if _, ok := allEvents[target]; ok { // 如果 target 為某個(gè)事件,則 callbackType 會(huì)置為與 after_[target] 相同,即二者等價(jià)
        callbackType = callbackAfterEvent
    }
}

還記得在上一篇文章中我提到過(guò),注冊(cè) closed 事件等價(jià)于 enter_closed 事件嗎?就是在 default 這個(gè) case 中實(shí)現(xiàn)的。

FSM EventFSM Event

對(duì)于構(gòu)造函數(shù)的講解就到這里,里面一些具體的代碼細(xì)節(jié)你可能現(xiàn)在有點(diǎn)發(fā)懵,沒(méi)關(guān)系,接著往下看,你的疑惑都將被解開(kāi)。

當(dāng)前狀態(tài)

接著,我們來(lái)看一下與當(dāng)前狀態(tài)相關(guān)的這幾個(gè)方法源碼是如何實(shí)現(xiàn)的,它們的代碼其實(shí)都很簡(jiǎn)單,我就不一一解讀了,我把源碼貼在這里,你一看就能明白:

// Current 返回 FSM 的當(dāng)前狀態(tài)。
func (f *FSM) Current() string {
    f.stateMu.RLock()
    defer f.stateMu.RUnlock()
    return f.current
}

// Is 判斷 FSM 當(dāng)前狀態(tài)是否為指定狀態(tài)。
func (f *FSM) Is(state string) bool {
    f.stateMu.RLock()
    defer f.stateMu.RUnlock()
    return state == f.current
}

// SetState 將 FSM 從當(dāng)前狀態(tài)轉(zhuǎn)移到指定狀態(tài)。
// 此調(diào)用不觸發(fā)任何回調(diào)函數(shù)(如果定義)。
func (f *FSM) SetState(state string) {
    f.stateMu.Lock()
    defer f.stateMu.Unlock()
    f.current = state
}

// Can 判斷 FSM 在當(dāng)前狀態(tài)下,是否可以觸發(fā)指定事件,如果可以,則返回 true。
func (f *FSM) Can(event string) bool {
    f.eventMu.Lock()
    defer f.eventMu.Unlock()
    f.stateMu.RLock()
    defer f.stateMu.RUnlock()
    _, ok := f.transitions[eKey{event, f.current}]
    return ok && (f.transition == nil)
}

func (f *FSM) Cannot(event string) bool {
    return !f.Can(event)
}

// AvailableTransitions 返回當(dāng)前狀態(tài)下可用的轉(zhuǎn)換列表。
func (f *FSM) AvailableTransitions() []string {
    f.stateMu.RLock()
    defer f.stateMu.RUnlock()
    var transitions []string
    for key := range f.transitions {
        if key.src == f.current {
            transitions = append(transitions, key.event)
        }
    }
    return transitions
}
狀態(tài)轉(zhuǎn)換

與狀態(tài)轉(zhuǎn)換相關(guān)的方法可以說(shuō)是 FSM 最重要的方法了。

我們先來(lái)看 Event 方法的實(shí)現(xiàn):

// Event 通過(guò)指定事件名稱觸發(fā)狀態(tài)轉(zhuǎn)換
func (f *FSM) Event(ctx context.Context, event string, args ...interface{}) error {
    f.eventMu.Lock() // 事件互斥鎖鎖定

    // 為了始終解鎖事件互斥鎖(eventMu),此處添加了 defer 防止?fàn)顟B(tài)轉(zhuǎn)換完成后執(zhí)行 enter/after 回調(diào)時(shí)仍持有鎖;
    // 因?yàn)檫@些回調(diào)可能觸發(fā)新的狀態(tài)轉(zhuǎn)換,故在下方代碼中需要顯式解鎖
    var unlocked bool// 標(biāo)記是否已經(jīng)解鎖
    deferfunc() {
        if !unlocked { // 如果下方的邏輯已經(jīng)顯式操作過(guò)解鎖,defer 中無(wú)需重復(fù)解鎖
            f.eventMu.Unlock()
        }
    }()

    f.stateMu.RLock() // 獲取狀態(tài)讀鎖
    defer f.stateMu.RUnlock()

    // NOTE: 之前的轉(zhuǎn)換尚未完成
    if f.transition != nil {
        // 上一次狀態(tài)轉(zhuǎn)換還未完成,返回"前一個(gè)轉(zhuǎn)換未完成"錯(cuò)誤
        return InTransitionError{event}
    }

    // NOTE: 事件 event 在當(dāng)前狀態(tài) current 下是否適用,即是否在 transitions 表中
    dst, ok := f.transitions[eKey{event, f.current}]
    if !ok { // 無(wú)效事件
        for ekey := range f.transitions {
            if ekey.event == event {
                // 事件和當(dāng)前狀態(tài)不對(duì)應(yīng)
                return InvalidEventError{event, f.current}
            }
        }
        // 未定義的事件
        return UnknownEventError{event}
    }

    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    // 構(gòu)造一個(gè)事件對(duì)象
    e := &Event{f, event, f.current, dst, nil, args, false, false, cancel}

    // NOTE: 執(zhí)行 before 鉤子
    err := f.beforeEventCallbacks(ctx, e)
    if err != nil {
        return err
    }

    // NOTE: 當(dāng)前狀態(tài)等于目標(biāo)狀態(tài),無(wú)需轉(zhuǎn)換
    if f.current == dst {
        f.stateMu.RUnlock()
        defer f.stateMu.RLock()
        f.eventMu.Unlock()
        unlocked = true
        // NOTE: 執(zhí)行 after 鉤子
        f.afterEventCallbacks(ctx, e)
        return NoTransitionError{e.Err}
    }

    // 定義狀態(tài)轉(zhuǎn)換閉包函數(shù)
    transitionFunc := func(ctx context.Context, async bool)func() {
        returnfunc() {
            if ctx.Err() != nil {
                if e.Err == nil {
                    e.Err = ctx.Err()
                }
                return
            }

            f.stateMu.Lock()
            f.current = dst    // 狀態(tài)轉(zhuǎn)換
            f.transition = nil// NOTE: 標(biāo)記狀態(tài)轉(zhuǎn)換完成
            f.stateMu.Unlock()

            // 顯式解鎖 eventMu 事件互斥鎖,允許 enterStateCallbacks 回調(diào)函數(shù)觸發(fā)新的狀態(tài)轉(zhuǎn)換操作(避免死鎖)
            // 對(duì)于異步狀態(tài)轉(zhuǎn)換,無(wú)需顯式解鎖,鎖已在觸發(fā)異步操作時(shí)釋放
            if !async {
                f.eventMu.Unlock()
                unlocked = true
            }
            // NOTE: 執(zhí)行 enter 鉤子
            f.enterStateCallbacks(ctx, e)
            // NOTE: 執(zhí)行 after 鉤子
            f.afterEventCallbacks(ctx, e)
        }
    }

    // 記錄狀態(tài)轉(zhuǎn)換函數(shù)(這里標(biāo)記為同步轉(zhuǎn)換)
    f.transition = transitionFunc(ctx, false)

    // NOTE: 執(zhí)行 leave 鉤子
    if err = f.leaveStateCallbacks(ctx, e); err != nil {
        if _, ok := err.(CanceledError); ok {
            f.transition = nil// NOTE: 如果通過(guò) ctx 取消了,則標(biāo)記為 nil,無(wú)需轉(zhuǎn)換
        } elseif asyncError, ok := err.(AsyncError); ok { // NOTE: 如果是 AsyncError,說(shuō)明是異步轉(zhuǎn)換
            // 為異步操作創(chuàng)建獨(dú)立上下文,以便異步狀態(tài)轉(zhuǎn)換正常工作
            // 這個(gè)新的 ctx 實(shí)際上已經(jīng)脫離了原始 ctx,原 ctx 取消不會(huì)影響當(dāng)前 ctx
            // 不過(guò)新的 ctx 保留了原始 ctx 的值,所有通過(guò) ctx 傳遞的值還可以繼續(xù)使用
            ctx, cancel := uncancelContext(ctx)
            e.cancelFunc = cancel                    // 綁定新取消函數(shù)
            asyncError.Ctx = ctx                     // 傳遞新上下文
            asyncError.CancelTransition = cancel     // 暴露取消接口
            f.transition = transitionFunc(ctx, true) // NOTE: 標(biāo)記為異步轉(zhuǎn)換狀態(tài)
            // NOTE: 如果是異步轉(zhuǎn)換,直接返回,不會(huì)同步調(diào)用 f.doTransition(),需要用戶手動(dòng)調(diào)用 f.Transition() 來(lái)觸發(fā)狀態(tài)轉(zhuǎn)換
            return asyncError
        }
        return err
    }

    // Perform the rest of the transition, if not asynchronous.
    f.stateMu.RUnlock()
    defer f.stateMu.RLock()
    err = f.doTransition() // NOTE: 執(zhí)行狀態(tài)轉(zhuǎn)換邏輯,即調(diào)用 f.transition()
    if err != nil {
        return InternalError{}
    }

    return e.Err
}

因?yàn)?nbsp;Event 是核心方法,所以源碼會(huì)比較多,我們一起來(lái)梳理下核心邏輯。

首先,Event 方法會(huì)判斷上一次的狀態(tài)轉(zhuǎn)換是否完成:

// NOTE: 之前的轉(zhuǎn)換尚未完成
if f.transition != nil {
    // 上一次狀態(tài)轉(zhuǎn)換還未完成,返回"前一個(gè)轉(zhuǎn)換未完成"錯(cuò)誤
    return InTransitionError{event}
}

是否轉(zhuǎn)換完成的標(biāo)志是 f.transition 是否為 nil,如果上一次狀態(tài)轉(zhuǎn)換尚未完成,則返回一個(gè) Sentinel Error。

接著,需要判斷當(dāng)前觸發(fā)的事件是否有效:

// NOTE: 事件 event 在當(dāng)前狀態(tài) current 下是否適用,即是否在 transitions 表中
dst, ok := f.transitions[eKey{event, f.current}]
if !ok { // 無(wú)效事件
    for ekey := range f.transitions {
        if ekey.event == event {
            // 事件和當(dāng)前狀態(tài)不對(duì)應(yīng)
            return InvalidEventError{event, f.current}
        }
    }
    // 未定義的事件
    return UnknownEventError{event}
}

前文中我們說(shuō)過(guò) f.transitions 用于記錄狀態(tài)轉(zhuǎn)換規(guī)則,即定義觸發(fā)某一事件時(shí),允許從某一種狀態(tài),轉(zhuǎn)換成另一種狀態(tài)。

如果在 f.transitions 表中查不到任何一條與當(dāng)前狀態(tài)和事件對(duì)應(yīng)的數(shù)據(jù),則表示無(wú)效事件,同樣會(huì)返回指定的 Sentinel Error。

這些檢查都通過(guò)后,就會(huì)構(gòu)造一個(gè)事件對(duì)象:

ctx, cancel := context.WithCancel(ctx)
defer cancel()
// 構(gòu)造一個(gè)事件對(duì)象
e := &Event{f, event, f.current, dst, nil, args, false, false, cancel}

接下來(lái),就到了狀態(tài)轉(zhuǎn)換的核心邏輯了。而所有的回調(diào)函數(shù),也是在這個(gè)時(shí)候開(kāi)始觸發(fā)執(zhí)行的。

在執(zhí)行狀態(tài)轉(zhuǎn)換之前,首先要執(zhí)行的就是 before 類回調(diào)函數(shù):

// NOTE: 執(zhí)行 before 鉤子
err := f.beforeEventCallbacks(ctx, e)
if err != nil {
    return err
}

執(zhí)行完 before 類回調(diào)函數(shù),會(huì)再對(duì)狀態(tài)做一次檢查:

// NOTE: 當(dāng)前狀態(tài)等于目標(biāo)狀態(tài),無(wú)需轉(zhuǎn)換
if f.current == dst {
    f.stateMu.RUnlock()
    defer f.stateMu.RLock()
    f.eventMu.Unlock()
    unlocked = true
    // NOTE: 執(zhí)行 after 鉤子
    f.afterEventCallbacks(ctx, e)
    return NoTransitionError{e.Err}
}

如果狀態(tài)機(jī)的當(dāng)前狀態(tài)等于目標(biāo)狀態(tài),則無(wú)需狀態(tài)轉(zhuǎn)換,那么直接執(zhí)行 after 類回調(diào)函數(shù)就行了,最終返回指定的 Sentinel Error。

否則,需要進(jìn)行狀態(tài)轉(zhuǎn)換。此時(shí),狀態(tài)轉(zhuǎn)換也不會(huì)直接進(jìn)行,而是會(huì)定義一個(gè)狀態(tài)轉(zhuǎn)換閉包函數(shù)并賦值給 f.transition:

// 定義狀態(tài)轉(zhuǎn)換閉包函數(shù)
    transitionFunc := func(ctx context.Context, async bool) func() {
        return func() {
            ...
        }
    }

    // 記錄狀態(tài)轉(zhuǎn)換函數(shù)(這里標(biāo)記為同步轉(zhuǎn)換)
    f.transition = transitionFunc(ctx, false)

狀態(tài)轉(zhuǎn)換函數(shù)第二個(gè)參數(shù)用來(lái)標(biāo)記同步轉(zhuǎn)換還是異步轉(zhuǎn)換,這里標(biāo)記為同步轉(zhuǎn)換。對(duì)于異步轉(zhuǎn)換邏輯,我們后面再來(lái)講解。

接下來(lái)會(huì)先執(zhí)行 leave 類的回調(diào)函數(shù):

// NOTE: 執(zhí)行 leave 鉤子
if err = f.leaveStateCallbacks(ctx, e); err != nil {
    ...
}

這是調(diào)用的第二個(gè)回調(diào)函數(shù)。

最后,終于到了執(zhí)行狀態(tài)轉(zhuǎn)換的邏輯了:

err = f.doTransition() // NOTE: 執(zhí)行狀態(tài)轉(zhuǎn)換邏輯,即調(diào)用 f.transition()
if err != nil {
    return InternalError{}
}

這里調(diào)用了 f.doTransition() 函數(shù),其定義如下:

// doTransition wraps transitioner.transition.
func (f *FSM) doTransition() error {
    return f.transitionerObj.transition(f)
}

可以發(fā)現(xiàn),其內(nèi)部正式調(diào)用了 f.transitionerObj 屬性的 transition 方法。

還記得 f.transitionerObj 屬性是何時(shí)賦值嗎?在 NewFSM 構(gòu)造函數(shù)中,其賦值如下:

// 構(gòu)造有限狀態(tài)機(jī) FSM
f := &FSM{
    transitionerObj: &transitionerStruct{},        // 狀態(tài)轉(zhuǎn)換器,使用默認(rèn)實(shí)現(xiàn)
    current:         initial,                      // 當(dāng)前狀態(tài)
    transitions:     make(map[eKey]string),        // 存儲(chǔ)「事件和原狀態(tài)」到「目標(biāo)狀態(tài)」的轉(zhuǎn)換規(guī)則映射
    callbacks:       make(map[cKey]Callback),      // 回調(diào)函數(shù)映射表
    metadata:        make(map[string]interface{}), // 元信息
}

所以我們需要看一下 transitionerStruct 的具體實(shí)現(xiàn):

// transitioner 是 FSM 的狀態(tài)轉(zhuǎn)換函數(shù)接口。
type transitioner interface {
    transition(*FSM) error
}

// 狀態(tài)轉(zhuǎn)換接口的默認(rèn)實(shí)現(xiàn)
type transitionerStruct struct{}

// Transition completes an asynchronous state change.
//
// The callback for leave_<STATE> must previously have called Async on its
// event to have initiated an asynchronous state transition.
func (t transitionerStruct) transition(f *FSM) error {
    if f.transition == nil {
        return NotInTransitionError{}
    }
    f.transition()
    returnnil
}

f.transitionerObj 屬性聲明的是 transitioner 接口類型,而 transitionerStruct 結(jié)構(gòu)體則是這個(gè)接口的默認(rèn)實(shí)現(xiàn)。

transitionerStruct.transition 方法內(nèi)部最終還是在調(diào)用 f.transition() 方法。

而 f.transition 方法,也就是前文中定義的那個(gè)閉包函數(shù):

// 定義狀態(tài)轉(zhuǎn)換閉包函數(shù)
transitionFunc := func(ctx context.Context, async bool)func() {
    returnfunc() {
        if ctx.Err() != nil {
            if e.Err == nil {
                e.Err = ctx.Err()
            }
            return
        }

        f.stateMu.Lock()
        f.current = dst    // 狀態(tài)轉(zhuǎn)換
        f.transition = nil// NOTE: 標(biāo)記狀態(tài)轉(zhuǎn)換完成
        f.stateMu.Unlock()

        // 顯式解鎖 eventMu 事件互斥鎖,允許 enterStateCallbacks 回調(diào)函數(shù)觸發(fā)新的狀態(tài)轉(zhuǎn)換操作(避免死鎖)
        // 對(duì)于異步狀態(tài)轉(zhuǎn)換,無(wú)需顯式解鎖,鎖已在觸發(fā)異步操作時(shí)釋放
        if !async {
            f.eventMu.Unlock()
            unlocked = true
        }
        // NOTE: 執(zhí)行 enter 鉤子
        f.enterStateCallbacks(ctx, e)
        // NOTE: 執(zhí)行 after 鉤子
        f.afterEventCallbacks(ctx, e)
    }
}

// 記錄狀態(tài)轉(zhuǎn)換函數(shù)(這里標(biāo)記為同步轉(zhuǎn)換)
f.transition = transitionFunc(ctx, false)

閉包函數(shù)的 async 參數(shù)用來(lái)標(biāo)記同步或異步,我們暫且不關(guān)心異步,這里只關(guān)注同步邏輯。

其實(shí),這里的核心邏輯就是完成狀態(tài)轉(zhuǎn)換:

f.current = dst    // 狀態(tài)轉(zhuǎn)換
f.transition = nil // NOTE: 標(biāo)記狀態(tài)轉(zhuǎn)換完成

狀態(tài)轉(zhuǎn)換完成后,將 f.transition 標(biāo)記為 nil。所以根據(jù)這個(gè)屬性的值,就能判斷上一次狀態(tài)轉(zhuǎn)換是否完成。

狀態(tài)轉(zhuǎn)換完成后,依次執(zhí)行 enter 和 after 類回調(diào)函數(shù):

// NOTE: 執(zhí)行 enter 鉤子
f.enterStateCallbacks(ctx, e)
// NOTE: 執(zhí)行 after 鉤子
f.afterEventCallbacks(ctx, e)

根據(jù) Event 方法的源碼走讀,我們可以總結(jié)出狀態(tài)轉(zhuǎn)換的核心流程如下:

FSM EventFSM Event

本小節(jié)最后再貼一下 Transition 方法的源碼:

// Transition wraps transitioner.transition.
func (f *FSM) Transition() error {
    f.eventMu.Lock()
    defer f.eventMu.Unlock()
    return f.doTransition()
}
回調(diào)函數(shù)

現(xiàn)在,我們來(lái)看一下回調(diào)函數(shù)的具體實(shí)現(xiàn):

// beforeEventCallbacks calls the before_ callbacks, first the named then the
// general version.
func (f *FSM) beforeEventCallbacks(ctx context.Context, e *Event) error {
    if fn, ok := f.callbacks[cKey{e.Event, callbackBeforeEvent}]; ok {
        fn(ctx, e)
        if e.canceled {
            return CanceledError{e.Err}
        }
    }
    if fn, ok := f.callbacks[cKey{"", callbackBeforeEvent}]; ok {
        fn(ctx, e)
        if e.canceled {
            return CanceledError{e.Err}
        }
    }
    returnnil
}

// leaveStateCallbacks calls the leave_ callbacks, first the named then the
// general version.
func (f *FSM) leaveStateCallbacks(ctx context.Context, e *Event) error {
    if fn, ok := f.callbacks[cKey{f.current, callbackLeaveState}]; ok {
        fn(ctx, e)
        if e.canceled {
            return CanceledError{e.Err}
        } elseif e.async { // NOTE: 異步信號(hào)
            return AsyncError{Err: e.Err}
        }
    }
    if fn, ok := f.callbacks[cKey{"", callbackLeaveState}]; ok {
        fn(ctx, e)
        if e.canceled {
            return CanceledError{e.Err}
        } elseif e.async {
            return AsyncError{Err: e.Err}
        }
    }
    returnnil
}

// enterStateCallbacks calls the enter_ callbacks, first the named then the
// general version.
func (f *FSM) enterStateCallbacks(ctx context.Context, e *Event) {
    if fn, ok := f.callbacks[cKey{f.current, callbackEnterState}]; ok {
        fn(ctx, e)
    }
    if fn, ok := f.callbacks[cKey{"", callbackEnterState}]; ok {
        fn(ctx, e)
    }
}

// afterEventCallbacks calls the after_ callbacks, first the named then the
// general version.
func (f *FSM) afterEventCallbacks(ctx context.Context, e *Event) {
    if fn, ok := f.callbacks[cKey{e.Event, callbackAfterEvent}]; ok {
        fn(ctx, e)
    }
    if fn, ok := f.callbacks[cKey{"", callbackAfterEvent}]; ok {
        fn(ctx, e)
    }
}

細(xì)心觀察,你會(huì)發(fā)現(xiàn)這幾個(gè)回調(diào)函數(shù)邏輯其實(shí)套路一樣,都是先匹配 cKey 的 target 值為 e.Event 回調(diào)函數(shù)來(lái)執(zhí)行,然后再匹配 target 值為 "" 的回調(diào)函數(shù)來(lái)執(zhí)行。

還記得 target 何時(shí)才會(huì)為空嗎?我們一起回顧下 NewFSM 中的代碼段:

// 根據(jù)回調(diào)函數(shù)名稱前綴分類
switch {
// 事件觸發(fā)前執(zhí)行
case strings.HasPrefix(name, "before_"):
    target = strings.TrimPrefix(name, "before_")
    if target == "event" { // 全局事件前置鉤子(任何事件觸發(fā)都會(huì)調(diào)用,如用于日志記錄場(chǎng)景)
        target = ""http:// 將 target 置空
        callbackType = callbackBeforeEvent
    } elseif _, ok := allEvents[target]; ok { // 在特定事件前執(zhí)行
        callbackType = callbackBeforeEvent
    }
// 離開(kāi)當(dāng)前狀態(tài)前執(zhí)行
case strings.HasPrefix(name, "leave_"):
    target = strings.TrimPrefix(name, "leave_")
    if target == "state" { // 全局狀態(tài)離開(kāi)鉤子
        target = ""
        callbackType = callbackLeaveState
    } elseif _, ok := allStates[target]; ok { // 離開(kāi)舊狀態(tài)前執(zhí)行
        callbackType = callbackLeaveState
    }

當(dāng) target 的值為 event/state 是,就會(huì)標(biāo)記為 ""。

所以,我們可以得出結(jié)論:xxx_event 或 xxx_state 回調(diào)函數(shù),會(huì)晚于 xxx_<EVENT> 或 xxx_<STATE> 而執(zhí)行。

那么,至此我們就理清了狀態(tài)轉(zhuǎn)換時(shí)所有的回調(diào)函數(shù)執(zhí)行順序:

FSM EventFSM Event

而這一結(jié)論,與我們?cè)谏弦黄恼轮兄v解的示例程序執(zhí)行輸出結(jié)果保持一致:

FSM EventFSM Event

此外,不知道你有沒(méi)有發(fā)現(xiàn),其實(shí)我在上一篇文章中挖了一個(gè)坑沒(méi)有詳細(xì)講解。

在前一篇文章中,我們定義了如下?tīng)顟B(tài)轉(zhuǎn)換規(guī)則:

fsm.Events{
    {Name: "open", Src: []string{"closed"}, Dst: "open"},
    {Name: "close", Src: []string{"open"}, Dst: "closed"},
},

細(xì)心的你可能已經(jīng)發(fā)現(xiàn),其實(shí)第一條規(guī)則中,事件和目標(biāo)狀態(tài),都叫 open;而第二條規(guī)則中,事件叫 close,目標(biāo)狀態(tài)叫 closed。

那么你有沒(méi)有思考過(guò),當(dāng)事件和目標(biāo)狀態(tài)同名時(shí),即在這里 open 既是 event 又是 state,那么定義如下回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)是屬于 event 還是 state 呢?

"open": func(_ context.Context, e *fsm.Event) {
    color.Green("| enter open\t | %s | %s |", e.Src, e.Dst)
},

我們知道,<NEW_STATE> 是 enter_<NEW_STATE> 的簡(jiǎn)寫(xiě)形式,而 <EVENT> 又是 after_<EVENT> 的簡(jiǎn)寫(xiě)形式。

我們還知道,這段邏輯是在 NewFSM 中的 default case 代碼中實(shí)現(xiàn)的:

// 處理未加前綴的回調(diào)(簡(jiǎn)短版本)
default:
    target = name                       // 狀態(tài)/事件
    if _, ok := allStates[target]; ok { // 如果 target 為某個(gè)狀態(tài),則 callbackType 會(huì)置為與 enter_[target] 相同
        callbackType = callbackEnterState
    } else if _, ok := allEvents[target]; ok { // 如果 target 為某個(gè)事件,則 callbackType 會(huì)置為與 after_[target] 相同
        callbackType = callbackAfterEvent
    }

而這段代碼中,優(yōu)先使用 allStates[target] 來(lái)匹配 target,即 open 會(huì)優(yōu)先當(dāng)作 state 來(lái)處理。

至此,關(guān)于回調(diào)函數(shù)的全部邏輯才算梳理完成。

元信息

FSM 對(duì)于元信息的操作非常簡(jiǎn)單,所有涉及元信息操作的方法源碼如下:

// Metadata 返回存儲(chǔ)在元信息中的值
func (f *FSM) Metadata(key string) (interface{}, bool) {
    f.metadataMu.RLock()
    defer f.metadataMu.RUnlock()
    dataElement, ok := f.metadata[key]
    return dataElement, ok
}

// SetMetadata 存儲(chǔ) key、val 到元信息中
func (f *FSM) SetMetadata(key string, dataValue interface{}) {
    f.metadataMu.Lock()
    defer f.metadataMu.Unlock()
    f.metadata[key] = dataValue
}

// DeleteMetadata 從元信息中刪除指定 key 對(duì)應(yīng)的數(shù)據(jù)
func (f *FSM) DeleteMetadata(key string) {
    f.metadataMu.Lock()
    delete(f.metadata, key)
    f.metadataMu.Unlock()
}

至于元信息有什么用,我將用一個(gè)示例進(jìn)行講解。

使用示例

對(duì)于 FSM 的元信息和異步狀態(tài)轉(zhuǎn)換操作,僅通過(guò)閱讀源碼,可能無(wú)法體會(huì)其使用場(chǎng)景。本小節(jié)將分別使用兩個(gè)示例對(duì)其進(jìn)行演示,以此來(lái)加深你的理解。

元信息使用

對(duì)于有限狀態(tài)機(jī)中元信息的使用,我寫(xiě)了一個(gè)使用示例:

https://github.com/jianghushinian/blog-go-example/blob/main/fsm/examples/data/data.go

package main

import (
    "context"
    "fmt"

    "github.com/looplab/fsm"
)

// NOTE: 將 FSM 作為生產(chǎn)者消費(fèi)者使用

func main() {
    fsm := fsm.NewFSM(
        "idle",
        fsm.Events{
            // 生產(chǎn)者
            {Name: "produce", Src: []string{"idle"}, Dst: "idle"},
            // 消費(fèi)者
            {Name: "consume", Src: []string{"idle"}, Dst: "idle"},
            // 清理數(shù)據(jù)
            {Name: "remove", Src: []string{"idle"}, Dst: "idle"},
        },
        fsm.Callbacks{
            // 生產(chǎn)者
            "produce": func(_ context.Context, e *fsm.Event) {
                dataValue := "江湖十年"
                e.FSM.SetMetadata("message", dataValue)
                fmt.Printf("produced data: %s\n", dataValue)
            },
            // 消費(fèi)者
            "consume": func(_ context.Context, e *fsm.Event) {
                data, ok := e.FSM.Metadata("message")
                if ok {
                    fmt.Printf("consume data: %s\n", data)
                }
            },
            // 清理數(shù)據(jù)
            "remove": func(_ context.Context, e *fsm.Event) {
                e.FSM.DeleteMetadata("message")
                if _, ok := e.FSM.Metadata("message"); !ok {
                    fmt.Println("removed data")
                }
            },
        },
    )

    fmt.Printf("current state: %s\n", fsm.Current())

    err := fsm.Event(context.Background(), "produce")
    if err != nil {
        fmt.Printf("produce err: %s\n", err)
    }

    fmt.Printf("current state: %s\n", fsm.Current())

    err = fsm.Event(context.Background(), "consume")
    if err != nil {
        fmt.Printf("consume err: %s\n", err)
    }

    fmt.Printf("current state: %s\n", fsm.Current())

    err = fsm.Event(context.Background(), "remove")
    if err != nil {
        fmt.Printf("remove err: %s\n", err)
    }

    fmt.Printf("current state: %s\n", fsm.Current())
}

在這個(gè)示例中,將 FSM 作為了生產(chǎn)者消費(fèi)者來(lái)使用。而數(shù)據(jù)的傳遞,正是通過(guò)元信息(FSM.metadata)來(lái)實(shí)現(xiàn)的。

? FSM.SetMetadata 用于設(shè)置元信息。

? FSM.Metadata 用于獲取元信息。

? FSM.DeleteMetadata 則用于清理元信息。

執(zhí)行示例代碼,得到輸出如下:

$ go run examples/data/data.go
current state: idle
produced data: 江湖十年
produce err: no transition
current state: idle
consume data: 江湖十年
consume err: no transition
current state: idle
removed data
remove err: no transition
current state: idle

可以發(fā)現(xiàn),在數(shù)據(jù)的傳遞過(guò)程中,我們得到了 no transition 錯(cuò)誤,而這個(gè)錯(cuò)誤其實(shí)我們之前有解讀過(guò),是在 Event 方法如下代碼段中產(chǎn)生的:

// NOTE: 當(dāng)前狀態(tài)等于目標(biāo)狀態(tài),無(wú)需轉(zhuǎn)換
if f.current == dst {
    f.stateMu.RUnlock()
    defer f.stateMu.RLock()
    f.eventMu.Unlock()
    unlocked = true
    // NOTE: 執(zhí)行 after 鉤子
    f.afterEventCallbacks(ctx, e)
    return NoTransitionError{e.Err}
}

因?yàn)?nbsp;FSM 的狀態(tài)始終是 idle,尚未發(fā)生狀態(tài)轉(zhuǎn)換,所以會(huì)返回 NoTransitionError 這個(gè) Sentinel Error。

所以,我們只需要忽略這個(gè) NoTransitionError,那么就能把狀態(tài)機(jī) FSM 當(dāng)作生產(chǎn)者消費(fèi)者來(lái)使用。

當(dāng)然要實(shí)現(xiàn)生產(chǎn)者消費(fèi)者功能我們有很多其他的選擇,這個(gè)示例主要是作為演示,讓我們能夠清晰的知道 FSM 提供的元信息功能如何使用。

異步示例

在 FSM 源碼解讀的過(guò)程中,我有意避而不談異步狀態(tài)轉(zhuǎn)換。是因?yàn)闆](méi)有示例的講解,直接閱讀源碼,不太容易理解。

我在這里為你演示一個(gè)示例,讓你來(lái)體會(huì)一下異步狀態(tài)轉(zhuǎn)換的用法:

https://github.com/jianghushinian/blog-go-example/blob/main/fsm/examples/async/async_transition.go

package main

import (
    "context"
    "errors"
    "fmt"

    "github.com/looplab/fsm"
)

// NOTE: 異步狀態(tài)轉(zhuǎn)換

func main() {
    // 構(gòu)造有限狀態(tài)機(jī)
    f := fsm.NewFSM(
        "start",
        fsm.Events{
            {Name: "run", Src: []string{"start"}, Dst: "end"},
        },
        fsm.Callbacks{
            // 注冊(cè) leave_<OLD_STATE> 回調(diào)函數(shù)
            "leave_start": func(_ context.Context, e *fsm.Event) {
                e.Async() // NOTE: 標(biāo)記為異步,觸發(fā)事件時(shí)不進(jìn)行狀態(tài)轉(zhuǎn)換
            },
        },
    )

    // NOTE: 觸發(fā) run 事件,但不會(huì)完整狀態(tài)轉(zhuǎn)換
    err := f.Event(context.Background(), "run")

    // NOTE: Sentinel Error `fsm.AsyncError` 標(biāo)識(shí)異步狀態(tài)轉(zhuǎn)換
    var asyncError fsm.AsyncError
    ok := errors.As(err, &asyncError)
    if !ok {
        panic(fmt.Sprintf("expected error to be 'AsyncError', got %v", err))
    }

    // NOTE: 主動(dòng)執(zhí)行狀態(tài)轉(zhuǎn)換操作
    if err = f.Transition(); err != nil {
        panic(fmt.Sprintf("Error encountered when transitioning: %v", err))
    }

    // NOTE: 當(dāng)前狀態(tài)
    fmt.Printf("current state: %s\n", f.Current())
}

示例中,在構(gòu)造有限狀態(tài)機(jī)對(duì)象 f 時(shí),為其注冊(cè)了 leave_start 回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)是異步狀態(tài)轉(zhuǎn)換的關(guān)鍵所在。其內(nèi)部通過(guò) e.Async() 將事件標(biāo)記為異步,這樣在事件觸發(fā)時(shí),就不會(huì)執(zhí)行狀態(tài)轉(zhuǎn)換邏輯。

接著,代碼中觸發(fā) run 事件。不過(guò)由于 e.Async() 的操作,事件觸發(fā)時(shí)不會(huì)進(jìn)行狀態(tài)轉(zhuǎn)換,而是返回 Sentinel Error fsm.AsyncError,這個(gè)錯(cuò)誤用于標(biāo)識(shí)這是一個(gè)異步操作,尚未進(jìn)行狀態(tài)轉(zhuǎn)換。

接下來(lái),我們主動(dòng)調(diào)用 f.Transition() 來(lái)執(zhí)行狀態(tài)轉(zhuǎn)換操作。

最終,打印 FSM 當(dāng)前狀態(tài)。

執(zhí)行示例代碼,得到輸出如下:

$ go run examples/async/async_transition.go 
current state: end

這個(gè)玩法,將觸發(fā)事件和狀態(tài)轉(zhuǎn)換操作進(jìn)行了分離,使得我們可以主動(dòng)控制狀態(tài)轉(zhuǎn)換的時(shí)機(jī)。

這個(gè)示例的關(guān)鍵步驟是在 leave_start 回調(diào)函數(shù)中的 e.Async() 邏輯,將當(dāng)前事件標(biāo)記為了異步。

首先,Event 對(duì)象其實(shí)也是一個(gè)結(jié)構(gòu)體,它有一個(gè)屬性 async,e.Async() 邏輯如下:

func (e *Event) Async() {
    e.async = true
}

而 leave_start 回調(diào)函數(shù),是在調(diào)用 *FSM.Event 方法時(shí)觸發(fā)的:

// NOTE: 執(zhí)行 leave 鉤子
if err = f.leaveStateCallbacks(ctx, e); err != nil {
    if _, ok := err.(CanceledError); ok {
        f.transition = nil// NOTE: 如果通過(guò) ctx 取消了,則標(biāo)記為 nil,無(wú)需轉(zhuǎn)換
    } elseif asyncError, ok := err.(AsyncError); ok { // NOTE: 如果是 AsyncError,說(shuō)明是異步轉(zhuǎn)換
        // 為異步操作創(chuàng)建獨(dú)立上下文,以便異步狀態(tài)轉(zhuǎn)換正常工作
        // 這個(gè)新的 ctx 實(shí)際上已經(jīng)脫離了原始 ctx,原 ctx 取消不會(huì)影響當(dāng)前 ctx
        // 不過(guò)新的 ctx 保留了原始 ctx 的值,所有通過(guò) ctx 傳遞的值還可以繼續(xù)使用
        ctx, cancel := uncancelContext(ctx)
        e.cancelFunc = cancel                    // 綁定新取消函數(shù)
        asyncError.Ctx = ctx                     // 傳遞新上下文
        asyncError.CancelTransition = cancel     // 暴露取消接口
        f.transition = transitionFunc(ctx, true) // NOTE: 標(biāo)記為異步轉(zhuǎn)換狀態(tài)
        // NOTE: 如果是異步轉(zhuǎn)換,直接返回,不會(huì)同步調(diào)用 f.doTransition(),需要用戶手動(dòng)調(diào)用 f.Transition() 來(lái)觸發(fā)狀態(tài)轉(zhuǎn)換
        return asyncError
    }
    return err
}

f.leaveStateCallbacks 就是在執(zhí)行 leave_start 回調(diào)函數(shù),其實(shí)現(xiàn)如下:

func (f *FSM) leaveStateCallbacks(ctx context.Context, e *Event) error {
    if fn, ok := f.callbacks[cKey{f.current, callbackLeaveState}]; ok {
        fn(ctx, e)
        if e.canceled {
            return CanceledError{e.Err}
        } else if e.async { // NOTE: 異步信號(hào)
            return AsyncError{Err: e.Err}
        }
    }
    ...
    return nil
}

這里最關(guān)鍵的一步就是在 else if e.async 時(shí),返回 Sentinel Error AsyncError。

而對(duì) f.leaveStateCallbacks(ctx, e) 的調(diào)用一旦返回 AsyncError,就說(shuō)明是要進(jìn)入異步狀態(tài)轉(zhuǎn)換邏輯。

此時(shí)會(huì)為 f.transition 重新賦值,并標(biāo)記為異步狀態(tài)轉(zhuǎn)換:

f.transition = transitionFunc(ctx, true) // NOTE: 標(biāo)記為異步轉(zhuǎn)換狀態(tài)
// NOTE: 如果是異步轉(zhuǎn)換,直接返回,不會(huì)同步調(diào)用 f.doTransition(),需要用戶手動(dòng)調(diào)用 f.Transition() 來(lái)觸發(fā)狀態(tài)轉(zhuǎn)換
return asyncError

并且返回 asyncError,這次 Event 事件觸發(fā)就完成了。不過(guò)并沒(méi)有接著去執(zhí)行 f.transition() 邏輯。所以就實(shí)現(xiàn)了異步操作。

到這里,異步轉(zhuǎn)換狀態(tài)的邏輯,我就幫你梳理完成了。這塊可能不太好理解,但是你跟著我的思路,執(zhí)行一遍示例代碼,然后深入到源碼,按照流程再梳理一遍,相信就就一定能理解了。

總結(jié)

本篇文章我?guī)阃暾喿x了有限狀態(tài)機(jī)的核心源碼,為你理清了 FSM 的設(shè)計(jì)思路和它提供的能力。讓你能夠知其然,也能知其所以然。

并且我還針對(duì)不太常用的元信息操作和異步狀態(tài)轉(zhuǎn)換,提供了使用示例。其實(shí)官方 examples 中提供了好幾個(gè)示例,你可以自行看一下,學(xué)完了本文源碼,再去看示例就是小菜一碟的事情了。

值得注意的是,因?yàn)樗械臓顟B(tài)轉(zhuǎn)換核心邏輯都加了互斥鎖,所以 FSM 是并發(fā)安全的。


責(zé)任編輯:武曉燕 來(lái)源: Go編程世界
相關(guān)推薦

2013-09-03 09:57:43

JavaScript有限狀態(tài)機(jī)

2019-11-08 16:05:54

Promise前端鏈?zhǔn)秸{(diào)用

2024-03-11 08:47:30

CRDT數(shù)據(jù)類型協(xié)同編輯

2019-09-12 09:40:34

秒殺系統(tǒng)高并發(fā)

2024-05-15 10:14:00

CRDT數(shù)據(jù)類型協(xié)同編輯

2022-03-06 19:57:50

狀態(tài)機(jī)easyfsm項(xiàng)目

2018-08-07 14:45:52

編程語(yǔ)言JavaScripthtml

2021-07-03 08:59:49

動(dòng)態(tài)代理JDK

2021-04-29 09:31:05

前端開(kāi)發(fā)技術(shù)

2014-07-18 17:14:16

小米蘋(píng)果雷軍

2021-08-29 08:14:30

GPU CSS gpu

2019-06-05 13:00:00

2021-09-07 06:40:26

狀態(tài)機(jī)識(shí)別地址

2023-04-12 07:14:31

Spring應(yīng)用業(yè)務(wù)

2024-05-20 00:00:00

代碼主線程

2016-03-31 17:01:26

桂林甲天下

2018-07-23 16:13:27

Google歐盟Android

2025-04-28 08:25:00

狀態(tài)機(jī)框架狀態(tài)機(jī)開(kāi)發(fā)

2025-04-09 10:36:32

2024-10-09 12:05:27

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

乱色588欧美| 最近2019中文字幕大全第二页 | 中文字幕人成不卡一区| 91精品在线观| 中文字幕亚洲高清| 色男人天堂综合再现| 日韩美女视频在线| 欧美视频免费播放| 黄网站免费在线观看| 北条麻妃一区二区三区| 国产精品国语对白| 久久久久99精品成人片毛片| 日本精品另类| 亚洲人亚洲人成电影网站色| 国产一区二区三区免费不卡| 久久福利一区二区| www.成年人| 绿色成人影院| 亚洲视频资源在线| 欧美极品视频一区二区三区| 国产a级免费视频| 国产精品综合| 欧美激情小视频| 少妇高潮在线观看| 猛男gaygay欧美视频| 日韩免费观看高清完整版 | 国产视频精品免费| 亚洲福利天堂| 日韩免费视频一区| 午夜啪啪小视频| 日韩影片中文字幕| 亚洲sss视频在线视频| 992tv成人免费观看| 成年人在线观看网站| 99在线精品一区二区三区| 成人免费淫片视频软件| 欧美另类高清videos的特点| 国产精品亚洲产品| 亚洲国产1区| 亚洲美女动态图120秒| 韩国三级丰满少妇高潮| 主播大秀视频在线观看一区二区| 精品欧美一区二区三区| 精品视频在线观看一区二区| 精品国产白色丝袜高跟鞋| 国产欧美日韩久久| 日本欧美色综合网站免费| 婷婷久久久久久| gogo大胆日本视频一区| 丁香五月网久久综合| 精品国产无码AV| 国产在线精品免费av| 成人h片在线播放免费网站| 国产又黄又猛又粗又爽| 久久精品毛片| 国产v综合v亚洲欧美久久| 亚洲成人第一网站| 老司机精品导航| 国产成人精品综合| 人人妻人人爽人人澡人人精品 | 91精品国产乱码久久久| 老司机免费视频一区二区| 国产精品欧美激情| 成人黄色三级视频| 美国欧美日韩国产在线播放| 国产精品日韩在线观看| 依依成人在线视频| 极品美女销魂一区二区三区免费| 成人激情视频网| 91国内精品视频| 国产精品自产自拍| 国产精品成人观看视频免费| 日韩av不卡播放| 国产原创视频在线| 久久精品一本| 国产一区私人高清影院| 国产剧情精品在线| av在线播放一区二区三区| 麻豆传媒一区| 永久av在线| 亚洲精品国久久99热| 成年人网站国产| 久久uomeier| 欧美日韩国产免费一区二区| www.欧美com| 岳的好大精品一区二区三区| 少妇高潮久久久久久潘金莲| 精品国产国产综合精品| 亚洲手机视频| 国产精品对白刺激| 精品人妻一区二区三区四区不卡 | 影院欧美亚洲| 国产精品国语对白| 午夜精品久久久久久久99热黄桃| 久久综合国产精品| 日本三日本三级少妇三级66| 日本蜜桃在线观看视频| 欧美日本国产一区| 日韩aaaaa| 国产精品99一区二区三区| 亚洲欧美日韩国产成人精品影院| 久久精品人人爽| 亚洲成人生活片| 久久综合激情| 99www免费人成精品| 欧洲一级在线观看| 亚洲视频免费观看| 国产精品99久久免费黑人人妻| 99久热在线精品视频观看| 亚洲欧美日本精品| 久久久久噜噜噜亚洲熟女综合| 日韩精品免费视频人成| 国产日韩欧美一区二区| 秋霞成人影院| 日本高清不卡视频| 国产一级黄色录像| 中文字幕一区二区三三| 国产91久久婷婷一区二区| 亚洲成人黄色片| 国产精品电影一区二区| 成年网站在线免费观看| 精品国产影院| 久久99久国产精品黄毛片入口| 午夜久久久久久久久久影院| eeuss鲁片一区二区三区在线观看| 成年人黄色在线观看| 日韩电影免费观| 亚洲国产精品女人久久久| 天天综合天天做| aaa在线视频| 国内久久精品| 91香蕉亚洲精品| 四虎久久免费| 在线亚洲一区观看| 中文字幕丰满孑伦无码专区| 尤物在线精品| 国产精品日韩高清| 性欧美猛交videos| 91精品国产全国免费观看 | 亚洲婷婷在线视频| 久久国产精品国产精品| 欧洲视频一区| 国产精品日本精品| 天堂а在线中文在线无限看推荐| 亚洲一区二区精品视频| 欧美一区二区三区影院| 女生裸体视频一区二区三区| 91影视免费在线观看| 男人天堂久久久| 精品视频在线看| 成人在线观看免费高清| 日本欧美一区二区三区乱码| 日本一区二区三区视频免费看| 成人性生活视频| 亚洲性69xxxbbb| 中文字幕天堂在线| 国产精品美女久久久久久久久| 婷婷六月天在线| 91蜜臀精品国产自偷在线 | 精品国产一区二区三区不卡蜜臂| 91欧美国产| 欧美中文在线观看国产| 青青草在线免费视频| 一本一道久久a久久精品| 爱爱免费小视频| 日韩黄色免费网站| 正在播放91九色| 免费观看性欧美大片无片| 欧美黑人xxxx| 五月婷婷丁香六月| 在线观看国产日韩| 娇小11一12╳yⅹ╳毛片| 国产在线精品不卡| 日韩精品视频在线观看视频| 日韩美女国产精品| 国产精品偷伦视频免费观看国产| 激情在线小视频| 精品久久久网站| 国产成人无码一区二区在线播放| 久久精品亚洲国产奇米99| 在线看的黄色网址| 欧美视频日韩| 日本不卡二区| 精品亚洲二区| 97在线免费观看视频| 成年在线电影| 亚洲第一av在线| 欧美在线视频精品| 亚洲成精国产精品女| 特级西西www444人体聚色 | 国产精品精品久久久| 国产91在线视频蝌蚪| 日韩成人av在线播放| 中文字幕+乱码+中文| 一区二区三区日韩精品视频| 精品无码一区二区三区| 激情成人综合网| 丰满少妇被猛烈进入高清播放| 日韩欧美大片| 国产在线一区二区三区欧美| 国产精品久久久久久久久久齐齐| 欧美丰满老妇厨房牲生活| 欧美精品a∨在线观看不卡| 69堂精品视频| 五月天婷婷导航| 一区二区三区欧美久久| 免费一级特黄3大片视频| 成人国产精品视频| 欧美在线a视频| 日韩影院免费视频| ww国产内射精品后入国产| 一本一本久久a久久综合精品| 欧美伦理一区二区| 99re6热只有精品免费观看| 国产精品久久久久久久久借妻| sm久久捆绑调教精品一区| 色老头一区二区三区在线观看| 香蕉视频网站在线| 日韩一区二区三区精品视频第3页| 911国产网站尤物在线观看| 91精选在线| 中文欧美日本在线资源| 深夜福利视频在线免费观看| 欧美本精品男人aⅴ天堂| 在线观看毛片av| 在线欧美一区二区| 国产精品人人人人| 亚洲国产aⅴ成人精品无吗| 外国一级黄色片| 精品无码国产一区二区三区51安| 国产在线观看一区二区| 日韩欧美国产片| 日韩电影在线一区| 国产免费成人在线| 99精品免费视频| 亚洲熟妇无码一区二区三区| 欧美三级在线| 久久福利一区二区| 欧美三区美女| 妺妺窝人体色777777| 国产综合亚洲精品一区二| 青少年xxxxx性开放hg| 91九色精品| 99热一区二区三区| 亚洲成人精选| 超碰在线免费观看97| 亚洲老妇激情| 91国在线高清视频| 欧美三区不卡| 欧美深夜福利视频| 美女爽到呻吟久久久久| 亚洲精品无码久久久久久| 国产精品美女| 久久久久久久久久久免费视频| 另类国产ts人妖高潮视频| 男人日女人bb视频| 日韩在线观看一区二区| 无限资源日本好片| 老司机午夜精品| 91视频免费入口| 国产传媒一区在线| 日本黄色录像片| 91免费看`日韩一区二区| 尤物视频最新网址| 国产精品成人免费精品自在线观看| 免费黄色国产视频| 一区二区三区四区精品在线视频| 久久久全国免费视频| 午夜精品久久久久久久99水蜜桃 | 亚洲视频福利| 亚洲在线视频观看| 91精品国产闺蜜国产在线闺蜜| 亚洲国产成人在线| 午夜免费激情视频| 精品国产乱码久久久久久天美| 性无码专区无码| 欧美视频精品在线| 99热这里是精品| 日韩精品日韩在线观看| av资源在线观看免费高清| 久久久精品国产| 国产福利电影在线播放| 国产成一区二区| 人人九九精品视频| 久久国产日韩欧美| 99精品综合| a在线视频观看| 精品亚洲免费视频| 波多野结衣先锋影音| 国产精品免费av| 国产精品9191| 欧美撒尿777hd撒尿| 成人1区2区3区| 国产亚洲一区二区精品| 欧美aaa免费| 国产精品揄拍500视频| 久久精品66| 综合操久久久| 美女精品一区| 日韩精品视频一区二区| 国产精品久久99| 国产一区二区99| 日韩免费在线观看| 北岛玲一区二区三区| 久久久综合av| 欧美欧美在线| 视频一区不卡| 亚洲在线成人| 精品人妻一区二区免费| 国产精品麻豆久久久| 精品成人免费视频| 日韩精品专区在线影院重磅| 成人网视频在线观看| 性欧美xxxx交| 8848成人影院| 亚洲最新在线| 日韩精品亚洲一区| 天堂久久久久久| 亚洲成人精品在线观看| 国产精品视频a| 色妞一区二区三区| 高清电影一区| 欧美日韩国产免费一区二区三区 | 国产精品久久久久久久久免费桃花 | 91国语精品自产拍在线观看性色| 亚洲欧美另类综合| 日韩av二区在线播放| 无码人妻精品一区二区三| 亚洲欧美自拍偷拍| 在线免费观看中文字幕| 亚洲香蕉av在线一区二区三区| 手机av在线| 精品婷婷色一区二区三区蜜桃| 午夜日韩电影| 久久久久久国产精品日本| 中文字幕一区二| 亚洲在线精品视频| 最近2019年好看中文字幕视频| 欧美日韩精品免费观看视完整| 蜜桃91精品入口| 亚洲男女自偷自拍| 巨胸大乳www视频免费观看| 欧美视频一区二区三区…| 日韩av资源| 国产成人一区三区| 国产亚洲一区| 日本成年人网址| 久久久精品中文字幕麻豆发布| 日产精品久久久| 亚洲欧洲午夜一线一品| 免费福利视频一区二区三区| 欧美在线一二三区| 日日摸夜夜添夜夜添精品视频 | 99产精品成人啪免费网站| 久久精品国产亚洲7777| 免费一级欧美片在线观看网站| 久久久久久久香蕉| 成人国产精品免费网站| 精品欧美一区二区三区免费观看 | 欧美美女视频在线观看| 久草中文在线观看| 亚洲自拍av在线| 亚洲毛片一区| 白白色免费视频| 欧美绝品在线观看成人午夜影视| 国产网友自拍视频导航网站在线观看| 亚洲一区二区三区久久| 雨宫琴音一区二区在线| 91视频免费观看网站| 欧美日韩国产小视频在线观看| 亚洲精品天堂| 九九九九九九精品| 免费欧美日韩国产三级电影| 久草手机视频在线观看| 精品卡一卡二卡三卡四在线| 吉吉日韩欧美| 91社在线播放| 91一区在线观看| 在线视频你懂得| 欧美激情性做爰免费视频| 自拍亚洲一区| 天天摸天天舔天天操| 五月婷婷激情综合| 在线看的av网站| 国产一区二区高清不卡| 青椒成人免费视频| 深夜福利影院在线观看| 亚洲美女自拍视频| 国产精品va视频| 欧美一级黄色片视频| 亚洲色图.com| 欧美香蕉爽爽人人爽| 3d蒂法精品啪啪一区二区免费| 免费亚洲婷婷| 欧美日韩三级在线观看| 这里只有视频精品| 久久人人爽人人爽人人片av不| 色一情一区二区| 欧美日韩国产精品一区二区三区四区| 日批视频免费观看|