Go 語(yǔ)言 fsm 源碼解讀,這一次讓你徹底學(xué)會(huì)有限狀態(tài)機(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ī)。
// 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)體定義:
FSM
接下來(lái),我將對(duì) FSM 結(jié)構(gòu)體所實(shí)現(xiàn)的方法進(jìn)行講解。
方法
我們先來(lái)看一下 FSM 結(jié)構(gòu)體都提供了哪些方法和能力:
FSM
這里列出了 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 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 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 Event
而這一結(jié)論,與我們?cè)谏弦黄恼轮兄v解的示例程序執(zhí)行輸出結(jié)果保持一致:
FSM 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ā)安全的。






























