最佳實踐之有限狀態機

有限狀態機(Finite State Machine,FSM),簡稱狀態機。今天這篇文檔的主體思路,來自本人受權的一項發明專利。第一次嘗試寫出來,但願分享給更多人。html

我當時寫這個專利的時候,太有感受了。很是的激動,同時我也很想分享給同事,可是可能太抽象了,未果。而後我想申請優秀專利獎,沒有渠道!因此最近刷屏的屠呦呦沒有評上院士的消息,我聽後,心想意料之中吧。當年在學校寫論文那個叫痛苦,說實話,我真的沒感受,後面終於東拼西湊,勉強過關。我媽說要我去讀博士,我不敢了!同窗建議我去大學當老師,我也不敢,我真怕誤人子弟,我本身都懷疑本身讀的書怎麼用,徹底沒多少實踐啊,雖然當時我也參加了導師的項目,用Delphi作了個界面。可是那叫實踐嗎,反正當時就是沒感受,沒開竅。而通過這些年的摸爬打滾,幾年後分紅兩次寫了幾篇專利,如今都已受權。這些專利都是很是有感受,不是帶任務的那種,因此基本上是一鼓作氣。其中有三篇是圍繞一個主題從不一樣角度寫的,太有感受了。我當時想,我如今應該對得起這張文憑了!大家說若是我如今去大學當老師,會有人要嗎?我以爲難,如今當老師都要求博士了,還要留過洋的,或者博士後了吧,因此不想了。。。不過我輩仍需努力吧,說不定哪天你不是坐在評委席上,就是坐在候選人席上:)編程

第一次知道狀態機,仍是在華爲作測試,測傳統的通訊產品。從一個傳統的通訊協議裏面知道了這個名字,當時只是以爲這個名字挺好的,當時沒想太多,因此具體是哪一個通訊協議我也忘了,固然仍是記住了其中的基本思路。幾年後,在這裏重構咱們的軟件時,在多線程中,創新性的運用了這個機制。很是好的一種方法,用了這種方法後基本沒有亂七八糟的Bug,並且好擴展,很容易加功能!而其實在以前解決死鎖的問題時,雛形就已經出來了。因此能夠先閱讀下當時我是怎麼解決死鎖問題的,有助於你的理解。數組

而最近我在參加一些架構師公開課的時候,發現老師們都在使用這個機制了。可是看不少學員有點懵懂,因此我就想哪天把它寫出來。同時也說明了,理解和使用狀態機須要有必定的編程基礎,有些抽象。但讀懂了,收穫會很多!太好用了! session

 

狀態機,表示有限個狀態以及在這些狀態之間的轉移和動做等行爲的數學模型。多線程

確切的描述是它是一個有向圖形,由一組節點和一組相應的轉移函數組成。狀態機經過響應一系列事件而「運行」。每一個事件都在屬於「當前」 節點的轉移函數的控制範圍內,其中函數的範圍是節點的一個子集。函數返回「下一個」(也許是同一個)節點。這些節點中至少有一個必須是終態。當到達終態,狀態機中止。架構

能夠理解爲,系統的行爲若是在不一樣的時間或環境下,其工做不一樣,而且行爲能夠分紅有限的狀態以及不重疊的程序塊時,系統顯現出了狀態行爲。正是由於狀態機具備有限個狀態,因此在實際工程中能夠實現。但也並不意味着只能進行有限次處理,相反,有限狀態機是閉環系統,有限無窮,能夠用有效的狀態處理無窮的事務。框架

更通俗點,狀態機是經過有限且充分的狀態個數記錄線程的各類操做行爲,並能在各類環境下、不一樣事件、不一樣時間的驅動下,切換到下一個合適的狀態,如此反覆。函數

總之,狀態機,是一種對流程控制進行抽象的方法。post

咱們舉一個常見的TCP鏈接對文件進行讀操做的例子進行說明。性能

 

首先咱們把整個流程分解成五個階段: Session不可用階段、Session創建階段、驗證階段、文件處理階段、Session終止階段。

這一步很關鍵,儘可能將粒度下降,抽象出盡量多的狀態。並且也能夠分層,進入文件處理階段,又能夠細分爲文件的打開、循環讀、文件關閉至少三個階段。

固然本文,主要圍繞Session層來展開描述。因此若是按正常的流程,這五步的執行狀況以下圖。

    

咱們不少人,可能將這五步寫在一個函數中,把全部的步驟放在一個函數中按順序執行,再加文件處理階段一個大的while循環。

固然邏輯上這樣也沒有問題,可是有兩點值得改進

(1)代碼的可複用性基本上沒有和可讀性不佳;

(2)更主要的是沒有真正的對這個五個階段進行抽象化。

因而,有人想到了對流程控制進行修改,中間可能的失敗直接日後跳到某個階段,再也不徹底是順序執行,以下圖。

    

綠色的線爲變動的地方。這種方法,對比前面思考的稍微多一些,但仍是不夠充分。上面提出的兩點改進,並無實質變化。

程序的流程控制是任意一個環節均可能存在失敗,並且最好還能夠重來,也就是說不必定只能日後結束或者退出,而是能夠自我更正或者恢復。以下圖。

    

 

紅色的線爲變動的地方。驗證階段,若是第一次失敗了,是否存在多個用戶名密碼呢。文件處理階段,若是當前文件讀取失敗,是否能夠讀下一個文件呢。

那有的人會說,問題也來了,若是都放在一個函數中,那豈不是又要加循環。若是兩次往回判斷,得加兩個while循環!這樣的推斷沒問題。但同時解決方法也是有的。

也就是回到了開始提到的兩點改進,對策以下

首先,將全部的步驟放在一個函數裏面,這是停留在面向過程的編碼,不是面向對象。因此,第一步須要將這個五步分別創建五個方法。這樣每一個方法均可以複用,即便將五個方法按順序執行,最少的代碼可能只需五行代碼,至少也看不到文件處理階段的while循環了,代碼量是否是大大減小。可讀性是否是更佳,一看方法名就知道在作什麼,而不須要看大段代碼,不須要關注實現細節。

其次,將五個階段定義五個值,一個枚舉變量的五個值,最好是定義五個宏,爲了可讀性。那麼加上switch和case,外面再加上一個while循環,注意僅僅須要一個while。是否是就能夠很方便的進行狀態切換,不論是往前切仍是日後切、反覆切。這就是狀態機真正的精髓

咱們再重複下狀態機的設計過程。首先將流程抽象成幾個能夠重複能夠複用的步驟,每一個步驟儘可能單獨封裝成函數或者方法。而後再對這個幾個步驟或者說方法,定義一個枚舉變量,枚舉值對應的宏名稱和方法名能夠一一對應。那麼再就能夠在一個函數中,或者多線程的run函數中加上while、switch和case的流程控制,顯然每一個case對應了一個方法,再根據方法的返回值再判斷下一個狀態是什麼,再進入下一次的while和下一個case,如此反覆。

咱們貼一段僞代碼,示範一下。

bool bExit = 0; m_nStatus = P_INIT; while ( !bExit) { switch (m_nStatus) { case P_INIT: { E_P_ERR eErr = Init(); if (eErr == P_ERR_INIT)//初始化錯誤
 { m_nStatus = P_EXIT;//直接退出
 } else if (eErr == P_OK) { m_nStatus = P_OPEN; //正常執行下一步
 } //省去若干代碼
            break; } case P_OPEN: { E_P_ERR eErr = Open(); if (eErr == P_ERR_OPEN)//打開失敗
 { //m_nStatus = P_EXIT;//能夠省去,狀態未變化
                  continue;//再次嘗試打開,或者打開下一個文件
 } else if (eErr == P_OK) { m_nStatus = P_READ;//正常,能夠開始讀取文件
 } //省去若干代碼
            break; } //省去若干代碼
 } //省去若干代碼
}//while
                        

 從代碼能夠看出,程序結構很是簡單,流程很是清晰,對狀態的轉換很是明確。

事實上,咱們在實現中,代碼遠不止這麼簡單。可是基本框架是相似的,更多的考慮是一些初始化、準備工做和一些異常處理工做。

而整個流程下來,你會發現這裏都用不上鎖,並且涉及到的每一個對象也用不上鎖: 

對session不須要加鎖,

對文件鏈表不須要加鎖,

對緩衝的讀寫不要加鎖,等等

最後整個流程除了維護狀態機自己的鎖之外,不須要任何鎖!

由於即便再複雜、再多的控制,對於狀態機而言,只是其中一個狀態而已!

而狀態機的鎖,主要是給外界瞭解和提供當前的狀態,因此通常也就是爲了getStatus函數須要而加上,因此使用不頻繁。

爽不爽,有沒有被顛覆的感受!:)

上一篇,提到過,儘可能少用鎖,我是否是兌現了個人諾言:)

 

最後,狀態機的本質是把流程抽象化,儘可能分解出可重複的步驟。最終能夠大大簡化流程,提升代碼的健壯性,基本達到無鎖實現,大大減小鎖帶來的性能消耗,提高性能;並且流程清晰,可讀性好!

狀態機適用於兩個狀態以上及須要重複使用的狀況,特別適用於多線程。

 

 

 

推薦閱讀:

經驗總結:內存泄露

經驗總結:死鎖 

經驗總結:如何用巧力解決問題 

經驗總結:如何把Bug的偶現變必現

相關文章
相關標籤/搜索