Go中的有限狀態機FSM的詳細介紹

来源:https://www.cnblogs.com/huageyiyangdewo/archive/2023/04/24/17351310.html
-Advertisement-
Play Games

這一年乾的很多事都是為了降低我的開源項目消息推送平臺austin使用門檻。 如果想學Java項目的,強烈推薦我的開源項目消息推送平臺Austin(8K stars) ,可以用作畢業設計,可以用作校招,可以看看生產環境是怎麼推送消息的。開源項目消息推送平臺austin倉庫地址: 消息推送平臺🔥推送下 ...


1、FSM簡介

1.1 有限狀態機的定義

有限狀態機(Finite State Machine,FSM)是一種數學模型,用於描述系統在不同狀態下的行為和轉移條件。

狀態機有三個組成部分:狀態(State)、事件(Event)、動作(Action),事件(轉移條件)觸髮狀態的轉移和動作的執行。動作的執行不是必須的,可以只轉移狀態,不指定任何動作。總體而言,狀態機是一種用以表示有限個狀態以及這些狀態之間的轉移和動作的執行等行為的數學模型。

狀態機可以用公式 State(S) , Event(E) -> Actions (A), State(S’)表示,即在處於狀態S的情況下,接收到了事件E,使得狀態轉移到了S’,同時伴隨著動作A的執行。

Event(事件)是指觸髮狀態轉換的輸入信號或條件。它可以是任何類型的輸入,例如感測器數據、用戶輸入、網路消息等。在編程中,Event通常是一個枚舉類型,每個枚舉值代表一個特定的事件。

State(狀態)是指系統在某一時刻所處的狀態,它是系統的一種抽象描述。在有限狀態機中,狀態是由一組狀態變數來描述的,這些狀態變數的取值決定了系統的狀態。狀態可以是離散的,也可以是連續的。在有限狀態機中,狀態通常用一個圓圈來表示,圓圈內部寫上狀態的名稱。例如,一個簡單的有限狀態機可以有兩個狀態:開和關,它們可以用以下方式表示:

Action(動作)是指在狀態轉移時執行的操作或動作。當有限狀態機從一個狀態轉移到另一個狀態時,可以執行一個或多個action來改變系統的狀態或執行某些操作。例如,當有限狀態機從“待機”狀態轉移到“運行”狀態時,可以執行一個action來啟動系統。在實際應用中,action可以是任何有效的代碼,例如函數調用、變數賦值、列印輸出等。

FSM 通常用於編程中,用於實現狀態轉移和控制流程。

註意:

在任何時刻,FSM 只能處於一種狀態。

1.2 Go中的FSM

通過上面關於有限狀態機的定義,我們大概知道了狀態機是個什麼東西,那麼Golang中是怎麼實現的呢。不用慌,已經有大佬實現好了,只管用就好了。

安裝:

go get github.com/looplab/[email protected]

接下來一起看看github.com/looplab/fsm 是如何使用的。

2、github.com/looplab/fsm 如何使用

註意:

不同版本的 fsm 使用方式,可能不太一樣,最好是看下 NewFSM 函數的註釋,看下具體的細節。 本篇文章以:github.com/looplab/[email protected] 為例。

2.1 fsm 基礎使用

這裡把官方的例子改了下,感覺官方的例子不是很清晰。代碼如下:

package main

import (
	"context"
	"fmt"

	"github.com/looplab/fsm"
)

type Door struct {
	Name  string
	FSM *fsm.FSM
}

func NewDoor(name string) *Door {
	d := &Door{
		Name: name,
	}

	d.FSM = fsm.NewFSM(
		"closed",
		fsm.Events{
			{Name: "open", Src: []string{"closed"}, Dst: "open"},
			{Name: "close", Src: []string{"open"}, Dst: "closed"},
		},
		fsm.Callbacks{
			"enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },
		},
	)

	return d
}

func (d *Door) enterState(e *fsm.Event) {
	fmt.Printf("The door's name:%s , current state:%s\n", d.Name, e.Dst)
}

func main() {
	door := NewDoor("測試")

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

	err := door.FSM.Event(context.Background(), "open")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())

	err = door.FSM.Event(context.Background(), "close")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())
}

執行結果:

fsm current state: closed 
The door's name:測試 , current state:open
fsm current state: open 
The door's name:測試 , current state:closed
fsm current state: closed

這裡就通過Event改變FSM中的狀態。轉移公式為:Src,Event -> Dst,d.enterState。大意就是接受到了輸入Event,狀態機的StateSrc->Dst,並且執行了Action:d.enterState。

2.2 fsm 中 Action 何時執行?

剛開始使用的時候,好奇d.enterState(e)是什麼時候調用的,我們一起看看 NewFSM 中的註釋就清楚了。

// NewFSM constructs a FSM from events and callbacks.
//
// The events and transitions are specified as a slice of Event structs
// specified as Events. Each Event is mapped to one or more internal
// transitions from Event.Src to Event.Dst.
// Callbacks are added as a map specified as Callbacks where the key is parsed
// as the callback event as follows, and called in the same order:
//
// 1. before_<EVENT> - called before event named <EVENT>
//
// 2. before_event - called before all events
//
// 3. leave_<OLD_STATE> - called before leaving <OLD_STATE>
//
// 4. leave_state - called before leaving all states
//
// 5. enter_<NEW_STATE> - called after entering <NEW_STATE>
//
// 6. enter_state - called after entering all states
//
// 7. after_<EVENT> - called after event named <EVENT>
//
// 8. after_event - called after all events
//
// There are also two short form versions for the most commonly used callbacks.
// They are simply the name of the event or state:
//
// 1. <NEW_STATE> - called after entering <NEW_STATE>
//
// 2. <EVENT> - called after event named <EVENT>
//
// If both a shorthand version and a full version is specified it is undefined
// which version of the callback will end up in the internal map. This is due
// to the pseudo random nature of Go maps. No checking for multiple keys is
// currently performed.

從上面我們知道了,d.enterState(e) 是在called after entering all states 時執行的。

2.2.1 完整版書寫的Callbacks執行順序

從上面的註釋能知道完整版書寫的Callbacks的執行順序如下:

2.2.2 簡寫版的Callbacks執行順序

2.2.3 註意事項

雖然Callbacks的寫法有兩種,但是不能同時使用完整版和簡寫版,否則最終使用那個版本是不確定的。

2.3 較為完整的例子

package main

import (
	"context"
	"fmt"

	"github.com/looplab/fsm"
)

type Door struct {
	Name  string
	FSM *fsm.FSM
}

func NewDoor(name string) *Door {
	d := &Door{
		Name: name,
	}

	d.FSM = fsm.NewFSM(
		"closed",
		fsm.Events{
			{Name: "open", Src: []string{"closed"}, Dst: "open"},
			{Name: "close", Src: []string{"open"}, Dst: "closed"},
		},
		fsm.Callbacks{
			"before_open": func(_ context.Context, e *fsm.Event) { d.beforeOpen(e) },
			"before_event": func(_ context.Context, e *fsm.Event) { d.beforeEvent(e) },
			"leave_closed": func(_ context.Context, e *fsm.Event) { d.leaveClosed(e) },
			"leave_state": func(_ context.Context, e *fsm.Event) { d.leaveState(e) },
			"enter_open": func(_ context.Context, e *fsm.Event) { d.enterOpen(e) },
			"enter_state": func(_ context.Context, e *fsm.Event) { d.enterState(e) },
			"after_open": func(_ context.Context, e *fsm.Event) { d.afterOpen(e) },
			"after_event": func(_ context.Context, e *fsm.Event) { d.afterEvent(e) },
		},
	)

	return d
}

func (d *Door) beforeOpen(e *fsm.Event) {
	fmt.Printf("beforeOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) beforeEvent(e *fsm.Event) {
	fmt.Printf("beforeEvent, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) leaveClosed(e *fsm.Event) {
	fmt.Printf("leaveClosed, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) leaveState(e *fsm.Event) {
	fmt.Printf("leaveState, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}


func (d *Door) enterOpen(e *fsm.Event) {
	fmt.Printf("enterOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}


func (d *Door) enterState(e *fsm.Event) {
	fmt.Printf("enterState, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}


func (d *Door) afterOpen(e *fsm.Event) {
	fmt.Printf("afterOpen, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}

func (d *Door) afterEvent(e *fsm.Event) {
	fmt.Printf("afterEvent, current state:%s, Dst:%s \n", d.FSM.Current(), e.Dst)
}



func main() {
	door := NewDoor("測試")

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

	err := door.FSM.Event(context.Background(), "open")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())

	err = door.FSM.Event(context.Background(), "close")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fsm current state: %s \n", door.FSM.Current())
}

執行結果:大家重點看current state何時發生的變化。

fsm current state: closed 
beforeOpen, current state:closed, Dst:open 
beforeEvent, current state:closed, Dst:open 
leaveClosed, current state:closed, Dst:open 
leaveState, current state:closed, Dst:open 
enterOpen, current state:open, Dst:open 
enterState, current state:open, Dst:open 
afterOpen, current state:open, Dst:open 
afterEvent, current state:open, Dst:open 
fsm current state: open 
beforeEvent, current state:open, Dst:closed 
leaveState, current state:open, Dst:closed 
enterState, current state:closed, Dst:closed 
afterEvent, current state:closed, Dst:closed 
fsm current state: closed 

參考資料:

looplab/fsm 源碼閱讀

有限狀態機FSM

深入淺出理解有限狀態機

[有限狀態機](


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • /*是否帶有小數*/ function isDecimal(strValue ) { var objRegExp= /^\d+\.\d+$/; return objRegExp.test(strValue); } /*校驗是否中文名稱組成 */ function ischina(str) { var ...
  • 最近,在 CodePen 上,看到一個非常有意思的圖片動效,效果如下: 原效果鏈接:CodePen Demo - 1 div pure CSS blinds staggered animation in 13 declarations 本身這個動畫效果,並沒有多驚艷。驚艷的地方在於原作者的實現方式非 ...
  • HTML學習筆記詳解 01 初識HTML HTML HTML,英文全稱為 Hyper Text Markup Language,中文翻譯為超文本標記語言,其中超文本包括:文字,圖片,音頻,視頻,動畫等 目前 目前主流使用的是HTML5+CSS3 HTML的優勢 主流瀏覽器都支持 微軟 GOOGLE ...
  • 當用戶在網頁中進行操作時,如點擊、滾動、輸入等,往往會頻繁地觸發事件。如果每個事件都立即執行相應的函數,可能會導致性能問題和用戶體驗不佳,因為這些函數可能需要執行複雜的操作,如計算、網路請求等。 為了優化這種情況,我們可以使用防抖和節流來限制函數的調用次數,從而提高性能和用戶體驗。 防抖 防抖是指在 ...
  • 簡介 原型模式(Prototype Pattern)是一種創建型設計模式,使你能夠複製已有對象,而無需使代碼依賴它們所屬的類,同時又能保證性能。 這種模式是實現了一個原型介面,該介面用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。 如果你需要複製一些對象,同時又希望代碼獨立於這 ...
  • 閱讀原文:https://bysocket.com/openai-chatgpt-vs-developer/ ChatGPT 能取代多少程式員的工作?導致我們程式員失業嗎?這是一個很好的話題,我這裡分享下: 一、ChatGPT 是什麼?有什麼作用 ChatGPT是一種基於人工智慧技術的語言模型,是可 ...
  • 環境:CentOS 7.6_x64Python版本:3.9.12FreeSWITCH版本 :1.10.9 一、背景描述 ESL庫是FreeSWITCH對外提供的介面,使用起來很方便,但該庫是基於C語言實現的,Python使用該庫的話需要使用源碼進行編譯。如果使用系統自帶的Python版本進行編譯,過 ...
  • 高級特性 主要內容 不安全 Rust 高級 Trait 高級 類型 高級函數和閉包 巨集 一、不安全 Rust 匹配命名變數 隱藏著第二個語言,它沒有強制記憶體安全保證:Unsafe Rust(不安全的 Rust) 和普通的 Rust 一樣,但提供了額外的“超能力” Unsafe Rust 存在的原因: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...