精品欧美一区二区三区在线观看 _久久久久国色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)

国产二区视频在线播放| 毛片精品免费在线观看| 久草综合在线观看| 成视频免费观看在线看| 国产高清在线观看免费不卡| 亚州成人av在线| 欧美丰满美乳xxⅹ高潮www| 日本一区二区三区中文字幕| 99热这里都是精品| 欧美黑人极品猛少妇色xxxxx| 成人性生生活性生交12| 国产91免费看| 日韩精品福利网| 久久成年人视频| 欧美老熟妇乱大交xxxxx | 美女黄网久久| 久久精品中文字幕| 国产精品边吃奶边做爽| 91精品亚洲一区在线观看| 欧美日韩国产色视频| 99视频免费观看| 污软件在线观看| 一区二区美女| 精品国产制服丝袜高跟| 色综合天天色综合| 中文日产幕无线码一区二区| 玉米视频成人免费看| 欧洲高清一区二区| 日韩在线一区二区三区四区| 国产一区999| 中文字幕无线精品亚洲乱码一区 | 婷婷成人激情| 91视频xxxx| 国产二区不卡| 国产三级三级在线观看| 日韩国产欧美在线观看| 久久男人的天堂| 欧美日韩亚洲国产另类| 久久中文视频| 国产亚洲成精品久久| 亚洲av成人无码一二三在线观看| 无码小电影在线观看网站免费 | 日韩午夜在线观看视频| 黑人粗进入欧美aaaaa| 天堂av中文在线观看| 五月综合激情日本mⅴ| 国产精品国三级国产av| gogogogo高清视频在线| 日韩国产一区二| 日产精品久久久一区二区福利| 亚洲精品一区二区三区影院忠贞| 日韩黄色在线| 欧美日韩不卡一区二区| 手机在线成人免费视频| 成人国产精品入口免费视频| 欧美在线观看视频在线| 国产特级黄色大片| 竹内纱里奈兽皇系列在线观看| 国产精品久久久久久久久免费相片 | 男女视频一区二区| 日韩免费在线视频| 一级黄色在线观看| 日韩av在线播放中文字幕| 久操成人在线视频| 国产一级二级三级| 清纯唯美亚洲综合一区| 中文字幕日韩专区| 免费在线观看成年人视频| 欧美视频第一| 在线成人高清不卡| 五月天六月丁香| 91久久偷偷做嫩草影院电| 日韩美女在线视频 | 国产福利免费在线观看| 久久影院视频免费| 国产高清精品一区二区三区| 欧美自拍偷拍第一页| 99久久婷婷国产| 日韩av电影在线观看| 欧美成年黄网站色视频| 国产午夜精品理论片a级大结局| 成人欧美一区二区三区在线观看| 中文字幕在线观看高清| 九色|91porny| 国产欧美一区二区白浆黑人| av免费观看网址| 99久久久无码国产精品| 欧美一区二区视频17c| 蜜桃av鲁一鲁一鲁一鲁俄罗斯的 | 国产精国产精品| 91成人一区二区三区| 国产不卡在线一区| 欧美日韩一区二区三| 精品国产99久久久久久| 国产精品久久免费看| 国产乱子伦精品视频| 在线最新版中文在线| 欧美久久久久中文字幕| 国产草草浮力影院| 欧美国产一区二区三区激情无套| 亚洲欧洲偷拍精品| 永久免费未视频| 亚洲成人av| 欧美亚洲另类视频| 99久久精品国产成人一区二区| 精品一区二区三区久久久| 韩国成人av| 麻豆传媒视频在线观看| 狠狠躁夜夜躁久久躁别揉| 亚洲妇熟xx妇色黄蜜桃| 中文字幕中文字幕精品| 欧美成在线观看| 中国精品一区二区| 久久超碰97人人做人人爱| 国产在线播放一区二区| 黄色av电影在线播放| 亚洲情趣在线观看| 成人在线观看a| 日韩在线无毛| 免费欧美网站| 日韩欧美一级二级三级久久久| 免费在线看黄色片| 怡红院在线观看| 欧美三级三级三级爽爽爽| 国产精品第七页| 午夜亚洲福利| 91久久久精品| 欧美69xxx| 欧美亚洲一区三区| 天堂av手机在线| 精品一区二区三| 热re99久久精品国产66热| 国模私拍视频在线| 久久欧美中文字幕| 成人性免费视频| 影视一区二区三区| 67194成人在线观看| 日韩视频在线观看免费视频| 亚洲自啪免费| 免费在线国产精品| 哥也色在线视频| 欧美日韩视频免费播放| 国产精品入口麻豆| 很黄很黄激情成人| 91成人免费看| 国产盗摄一区二区| 亚洲变态欧美另类捆绑| 国产在线观看成人| 久久精品久久精品| 亚洲图色在线| 成人永久在线| 欧美老女人性视频| 亚洲av无码国产精品久久不卡 | 欧美套图亚洲一区| 欧美午夜女人视频在线| 国产全是老熟女太爽了| 欧美中文日韩| 亚洲精品8mav| 高清久久精品| 久久久久久免费精品| 免费看黄色一级视频| 午夜精品免费在线观看| theporn国产精品| 欧美精品网站| 精品国产免费人成电影在线观...| 拍真实国产伦偷精品| 欧美一区国产二区| 久久久久久天堂| 91在线观看免费视频| 热久久精品免费视频| 久久要要av| 成人在线观看91| 亚洲第一av| 中文字幕日本精品| 国产日本精品视频| 国产精品久久久久影视| 一级黄色片在线免费观看| 在线欧美亚洲| 日韩中文不卡| 亚洲开心激情| 国产成人综合亚洲| 国产网友自拍视频导航网站在线观看| 色狠狠一区二区| 国产亚洲精品久久久久久豆腐| 久久青草久久| 咪咪色在线视频| 国产亚洲精品美女久久| 国产91色在线| av观看在线| 亚洲天堂男人的天堂| 99免费在线视频| 欧美性猛xxx| 亚洲色婷婷一区二区三区| 精品一区二区三区影院在线午夜| 亚洲日本精品国产第一区| 外国电影一区二区| 中文字幕国产亚洲| 影音先锋国产在线| 国产精品视频免费看| 超碰在线97免费| 亚洲特级毛片| 亚洲精品高清视频| 网红女主播少妇精品视频| 91久久在线观看| 免费观看成人性生生活片| 欧美精品一区三区| 人妻少妇精品无码专区| 欧美日韩mp4| 日韩女优一区二区| 国产不卡视频在线播放| www.com黄色片| 久久久久亚洲| 日本高清不卡一区二区三| 亚洲成av人片在线观看www| 国产精品久久久久久久久借妻| 91成人高清| 亚洲精品视频二区| 欧美一级视频免费| 日韩欧美一区二区久久婷婷| 国产一级二级毛片| 亚洲精品伦理在线| 国产午夜精品福利视频| 91麻豆6部合集magnet| 在线播放av网址| 国产九色精品成人porny| 国产精品一区二区羞羞答答| 亚洲在线观看| 免费一级特黄毛片| 在线精品一区二区| www.日本少妇| 国产一区美女| 日韩成人三级视频| 九色精品国产蝌蚪| 成人在线播放av| 蜜桃麻豆影像在线观看| 一区二区三区动漫| 青青草在线播放| 亚洲乱码一区av黑人高潮| 亚洲欧美日韩综合在线| 亚洲精品久久久久久久久| 中文字幕乱码人妻无码久久| 色哟哟欧美精品| 国内自拍视频在线播放| 动漫精品一区二区| av网站中文字幕| 91久久精品午夜一区二区| 无码人妻aⅴ一区二区三区有奶水| 亚洲少妇30p| 国产女片a归国片aa| 亚洲免费av观看| 青娱乐国产在线视频| 亚洲综合男人的天堂| 婷婷色一区二区三区| 久久精品在线观看| 蜜桃传媒一区二区亚洲| 日本一区二区不卡视频| 九九热久久免费视频| 亚洲婷婷在线视频| 精品无码久久久久| 精品久久久久久久久久久久久| 国产67194| 一二三四社区欧美黄| 日韩成人免费在线观看| 日韩欧美高清视频| 不卡的免费av| 欧美午夜视频一区二区| 麻豆成人在线视频| 午夜日韩在线电影| 青青草原国产视频| 亚州成人在线电影| 免费看一级视频| 欧美日韩国产一级片| 国内精品偷拍视频| 亚洲激情自拍图| 成全电影播放在线观看国语| 日韩视频在线免费观看| 欧洲黄色一区| 日本aⅴ大伊香蕉精品视频| 成人福利片在线| 国产91亚洲精品一区二区三区| 日韩欧乱色一区二区三区在线| 国产成人高清激情视频在线观看| 国产精品蜜臀| 欧美高清在线播放| 成人一区福利| 91精品视频在线看| theporn国产在线精品| 欧洲一区二区日韩在线视频观看免费| 欧美天堂影院| 午夜老司机精品| 黄色国产精品| 国产高清视频网站| 成av人片一区二区| 成人18视频免费69| 日韩欧美国产免费播放| av男人天堂av| 亚洲人成在线免费观看| 制服丝袜在线播放| 欧美日韩福利在线观看| 二吊插入一穴一区二区| 99久久伊人精品影院| 欧美日韩中文字幕一区二区三区| 色涩成人影视在线播放| 狠狠干成人综合网| 中文字幕成人在线视频| 久久综合视频网| 麻豆一区二区三区精品视频| 欧美在线观看视频一区二区| 五月激情六月婷婷| 美女福利精品视频| 成人亚洲免费| 日本不卡二区高清三区| 极品日韩av| 黑人巨大猛交丰满少妇| 中文字幕精品—区二区四季| 成人精品在线看| 日韩精品最新网址| 视频在线观看你懂的| 欧美黑人性视频| 国产精品久久久久久av公交车| 99久热re在线精品视频| 婷婷综合亚洲| 国产高潮免费视频| 久久欧美一区二区| 中文字幕激情小说| 亚洲国产高清福利视频| 在线视频国产区| 亚洲iv一区二区三区| 乱亲女h秽乱长久久久| 麻豆映画在线观看| 久久se精品一区二区| 人妻熟人中文字幕一区二区| 一本一道波多野结衣一区二区| 国产又粗又长视频| 日韩欧美成人一区| av在线影院| 成人av在线亚洲| 99久久激情| 91日韩精品视频| 综合久久国产九一剧情麻豆| 亚洲香蕉在线视频| 这里只有精品久久| 久久免费资源| 在线不卡日本| 激情综合色播五月| 中文字幕电影av| 51精品视频一区二区三区| 国产精品一区二区三区视频网站| 韩剧1988免费观看全集| 久久久加勒比| 在线不卡日本| 国产麻豆成人传媒免费观看| 四虎永久免费在线| 日韩午夜中文字幕| а√在线天堂官网| 欧美高清一区二区| 天堂一区二区在线| 91麻豆制片厂| 欧美日韩国产一区在线| 视频在线不卡| 国产精品电影网站| 我不卡影院28| 韩国黄色一级片| 欧美性xxxx在线播放| 第一福利在线| 成人欧美一区二区三区黑人孕妇| 精品一区欧美| 日韩大片一区二区| 亚洲品质自拍视频| 天天射,天天干| 国产精品成人品| 亚洲精品888| 水蜜桃av无码| 亚洲一区二区三区爽爽爽爽爽| 亚洲自拍偷拍另类| 亚洲视频第一页| 蜜桃麻豆av在线| 亚洲欧洲一区二区| 国产成人精品三级| 国产剧情在线视频| 日韩中文字幕免费看| 免费一区二区三区四区| 日本一区二区精品视频| 国内外成人在线| 国产精品白丝喷水在线观看| 欧美三级电影网站| 日韩成人伦理| 日本一区视频在线观看| 国产精品自在在线| 亚洲自拍一区在线观看| 久久久999成人| 蜜乳av综合| 蜜桃视频无码区在线观看| 91黄色小视频| 日本在线观看大片免费视频| 欧洲精品码一区二区三区免费看| 久久黄色网页| 久久久久久久久久久久国产| 在线成人激情黄色| 国产+成+人+亚洲欧洲在线|