理解alivc_framework狀態機
背景:
對與現在元件狀態現狀以及轉換過程有點疑惑,自己邊熟悉程式碼邊思考。c++標準庫以及Boost庫並沒有提供這種關於狀態機類似的基類别範本,原因是因為外面類複雜多樣,不同類的狀態沒有共性。但是聚焦到音視訊模組,這些類有著類似的特性,因此我們可以簡單的總結和基類化一些基類别範本,供模組繼承使用。
正文:
這裡指的模組類,一般是比較大的功能模組的介面類或者代理類(常見的如decode,encode等)。一般下面有對應的impl實現類,功能附帶緩衝類等。由於是模組類是一個模組整體對外的表現,因此介面,狀態也比較多。因此從上層角度出發,統一規劃所有模組類進行統一表現,是個比較好的實現。同時也經常會遇到特殊不符合統一的設計的情況。需要特殊特化處理。
1.個人理解模組狀態分類:
a)構造態,(未初始化態uninit)
模組類一般比較大,成員較多。個人習慣建構函式不進行業務處理,不傳引數,不拋異常,僅僅對模組類內部成員進行引用,bind等互相關聯。完成構造過程。
b)初始化態,(inited)
一般是外面呼叫,如果僅僅是自己建構函式呼叫就比較雞肋了(還不如寫到建構函式裡面)。可以接受外部傳引數。根據外部引數返回Init成功或者失敗,或者拋異常,這都可行的。
c) 執行態,(run/start)
一般是外部呼叫,外面再初始化設定引數,或者初始化前後設定引數,開始執行。嚴格意義來說執行時期是不接受引數改變的(部分特殊支援執行態改引數也是可以的)。
d) 暫停態,(pause)
個人理解這也是非執行態,只不過保留了執行態的上下文而已。
以上就是我覺得一個元件必須有的4個必要的狀態,或者說是使用者必須需要關心的狀態。說明一點的就是,這裡是非同步的情況,當然如果有些同步元件設計比較簡單,比如是同步的介面,就是 init/doTask/uninit 就完成了工作,那肯定就沒有執行態和暫停態了。但是考慮到模組都是負責比較複雜的業務,如果是純同步的情況沒必要設計成模組,或者說純同步的模組實在不多我們暫不考慮。
2.模組切換函式
- constructor:模組肯定是由constructor構造了。這裡值得說明一下的就是,如果該模組是Service型別的,也就是說全域性單例,則建構函式不是由業務邏輯控制的。這種單例的情況,般預設認為已經構造好了。一般是通過靜態函式getInstance來獲取類即可。
- init(para):通過呼叫init函式,實現uninit => inited 狀態切換,如果是service,應該提供初始化形參,service返回初始化上下文context.
- start():通過呼叫start,實現inited=>running 狀態切換
- pause():通過呼叫pause,實現running=>pause狀態切換。
- flush():此函式不切換狀態,只是一個同步標識。呼叫後,組建所有非同步操作都給出與同步操作相同的結果。
- clear():與flush類似,此函式不切合狀態,是同步標識。呼叫後,組建直接清除所有非同步快取。
- resume(): pause=>running狀態切換。
- stop():running=>inited 狀態切換。注意如果是pause=>inited 情況,也是stop。值得一提的是,stop之後,不需要將組建快取和條件變數恢復到初始狀態。也就是說,內含flush() 或者 clear()操作。因為stop之後是inited狀態,允許上下快取堆積。
- uninit:inited=>uninit狀態。個人感覺呼叫習慣是不用相容stop功能。也就是說如果元件是running和pause狀態的話,可以不響應uninit函式。客戶如果想無論什麼情況直接析構,可以組合呼叫stop()和 uninit()。
- destructor: 析構元件。為了防止記憶體洩露,一般會內建呼叫stop() 和 uninit()。
另外,這裡需要對reset做一下說明,有時候客戶希望的reset是恢復到running的初始狀態,但是也有客戶reset到uninit 狀態。我個人覺得uninit 可以完成第二種業務。
總結一下如下圖所示:
3.兩種處理狀態方法
處理狀態無非就是看狀態和訊息是否匹配if(state can proess this msg)。有兩種處理方法,第一種是為每個狀態寫一個處理函式,這個處理函式判斷是否響應該訊息:
int processInitstateMsg(const Msg& msg)
{
switch (msg.type) {
case start:
start();
state = start_state;
break;
case uninit:
...
default:
// 狀態不對,不處理
break;
}
}
另一種方法,也可以給每個訊息寫一個響應處理函式(alivc_framework現在的實現):
int processMsg(const Msg& msg)
{
switch (msg.type) {
case start:
return onStartMsg(msg);
case ...
default:
// 未知訊息
Log.e("unknown msg type.");
}
}
int onStartMsg(const Msg& startMsg)
{
if(state ok)
{
start();
}
else
{
return state invalid.
}
}
對應這兩種設計,誰優誰劣很明顯可以看出。由於元件對於自身狀態型別是明確的,對於外部處理的訊息型別是未知的。如果先按照狀態進行篩選,則只需判斷一次即可(是否這個訊息符合我的狀態),如果按照第二種實現,那就需要判斷兩次了。第一次是processMsg裡面判斷訊息型別(switch msg type), 然後呼叫每個響應訊息的處理函式,然後在每個函式裡面判斷狀態(switch state),進而再進行訊息處理。
當然第二種做法並不是一無是處。它可以提供一個stateBase進行抽象,處理公共的訊息。
4.狀態基類
由於狀態很多,我們可以通過構造一個基類stateBase,來管理狀態。stateBase暴露模組切換函式,內部有三個邏輯組成:1.檢查狀態,2.具體實現,3.切換狀態。以stop為例:
StateBase::Stop()
{
//1.check state
if(state != pause && state != running)
{
return false;
}
// 2.call virtual func impl;
StopImpl();
// 3.make state
state = inited;
return true;
}
這樣派生類就可以直接實現impl 而無需關心狀態了。
不滿足狀態條件的元件如何處理?
實際應用中,有很多不滿足使用基類條件的情況。比如類內部有多個執行緒,並且多個執行緒有相互依賴關係,並不是一個簡單的Start、Stop能解決的; 又或者某個類是封裝的第三方庫的類,而且這個第三方庫定義的狀態機與我們之前假設的狀態機不同(例如AndroidMediaCodec Flush狀態是不能跳轉到Running態,必須要先Clear到Init態等等)。如果遇到這些比較棘手的情況,應該拋棄狀態基類的方法,自己實現狀態機,並且在對外介面中說明狀態切換的時機以及特性。
總結
從類設計角度來講,設計一個狀態機,維護自身狀態來保證呼叫和返回的結果是必要的。
由於每個類具體情況不同,需要每個元件自己實現維護自己的狀態機。
由於音視訊編排元件比較類似,可以提供統一的介面呈現狀態轉換。
編排元件可以抽象成一個基類統一維護,也可以自己處理,各有優劣。
相關文章
- 理解資料狀態管理
- HTTP狀態碼的理解HTTP
- 狀態機
- 前端狀態管理與有限狀態機前端
- 狀態模式的理解和示例模式
- 有限狀態機
- 深入理解Flink中的狀態
- 狀態機設計
- 理解 React 輕量狀態管理庫 UnstatedReact
- Flutter狀態管理之Provider的理解使用FlutterIDE
- 玩轉Spring狀態機Spring
- C#狀態機StatelessC#
- 【譯】Effective TensorFlow Chapter2——理解靜態和動態形狀APT
- 用狀態機寫輪播
- 有限狀態機(FSM)的使用
- Unity——有限狀態機FSM修改Unity
- 從人類行為的角度理解狀態管理
- [譯]開發類 redux 庫來理解狀態管理Redux
- PHP 有限狀態機使用說明PHP
- [Android] 狀態機 StateMachine 原始碼剖析AndroidMac原始碼
- Java的型別化狀態機Java型別
- 探索FSM (有限狀態機)應用
- BLE鏈路層狀態機初探
- Spring狀態機(FSM),讓訂單狀態流轉如絲般順滑Spring
- 【架構設計】無狀態狀態機在程式碼中的實踐架構
- SpringStateMachine狀態機之八-整合測試SpringMac
- SpringBoot系列——狀態機(附完整原始碼)Spring Boot原始碼
- BlueStore原始碼分析之事物狀態機原始碼
- Memcached 多執行緒和狀態機執行緒
- 不停機狀態下使用Django建立索引Django索引
- 使用列舉實現狀態機來優雅你的狀態變更邏輯
- 淺談TCP(1):狀態機與重傳機制TCP
- 深入理解JDK動態代理機制JDK
- HTML5歷史管理狀態機制HTML
- MongoDB原理:複製集狀態同步機制MongoDB
- React 狀態管理:狀態與生命週期React
- 狀態列
- 狀態碼