<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 第8章 編寫健壯的應用程序 | 翻譯: | 王飛 | |-----|-----| | 校對: | 連城 | 第7章講解了Erlang的錯誤處理機制。這一章我們來看看怎樣使用這些機制來構建健壯、容錯的系統。 ### 防范錯誤數據 回想一下在第??章(程序??.5)中描述的那個用來分析電話號碼的服務程序。它的主循環包含了以下代碼: ~~~ server(AnalTable) -> receive {From, {analyse,Seq}} -> Result = lookup(Seq, AnalTable), From ! {number_analyser, Result}, server(AnalTable); {From, {add_number, Seq, Key}} -> From ! {number_analyser, ack}, server(insert(Seq, Key, AnalTable)) end. ~~~ 以上的Seq是一個表示電話號碼的數字序列,如[5,2,4,8,9]。在編寫lookup/2和insert/3這兩個函數時,我們應檢查Seq是否是一個電話撥號按鍵字符[[1]](#)的列表。若不做這個檢查,假設Seq是一個原子項hello,就會導致運行時錯誤。一個簡單些的做法是將lookup/2和insert/3放在一個catch語句的作用域中求值: ~~~ server(AnalTable) -> receive {From, {analyse,Seq}} -> case catch lookup(Seq, AnalTable) of {'EXIT', _} -> From ! {number_analyser, error}; Result -> From ! {number_analyser, Result} end, server(AnalTable); {From, {add_number, Seq, Key}} -> From ! {number_analyser, ack}, case catch insert(Seq, Key, AnalTable) of {'EXIT', _} -> From ! {number_analyser, error}, server(AnalTable); % Table not changed NewTable -> server(NewTable) end end. ~~~ 注意,借助catch我們的號碼分析函數可以只處理正常情況,而讓Erlang的錯誤處理機制去處理badmatch、badarg、function_clause等錯誤。 一般來說,設計服務器時應注意即使面對錯誤的輸入數據,服務器也不會“崩潰”。很多情況下發送給服務器的數據都來自服務器的訪問函數。在上面的例子中,號碼分析服務器獲悉的客戶端進程標識From是從訪問函數獲得的,例如: ~~~ lookup(Seq) -> number_analyser ! {self(), {analyse,Seq}}, receive {number_analyser, Result} -> Result end. ~~~ 服務器不需要檢查From是否是一個進程標識。在這個案例中,我們(借助訪問函數)來防范意外的錯誤情況。然而惡意程序仍然可以繞過訪問函數,向服務器發送惡意數據致使服務器崩潰: ~~~ number_analyser ! {55, [1,2,3]} ~~~ 這樣一來號碼分析器將試圖向進程55發送分析結果,繼而崩潰。 ### 健壯的服務進程 講解可靠服務進程設計的最好方法就是借助實例。 第??章(程序??.6)給出了一個資源分配器。對于這個分配器,如果一個資源被分配給了進程,而這個進程在釋放資源之前終止(無論是出于意外還是正常終止),那么這個資源就無法被收回。這個問題可以通過以下的方法來解決: - 令服務程序捕捉EXIT信號(process_flag(trap_exit,true))。 - 在分配器和申請資源的進程之間建立連接。 - 處理由這些進程發出的EXIT信號。 正如圖 8.1 所示。 ![_images/8.1.png](https://box.kancloud.cn/2015-09-04_55e90af032257.png) 圖8.1 健壯的分配器進程和客戶進程 分配器的訪問函數不變。通過以下方式啟動分配器: ~~~ start_server(Resources) -> process_flag(trap_exit, true), server(Resources, []). ~~~ 為了接收EXIT信號,我們將 “服務器” 循環改為: ~~~ server(Free, Allocated) -> receive {From,alloc} -> allocate(Free, Allocated, From); {From,{free,R}} -> free(Free, Allocated, From, R); {'EXIT', From, _ } -> check(Free, Allocated, From) end. ~~~ 為了跟申請資源(如果還有資源可用)的進程建立連接,還需要修改allocate/3 。 ~~~ allocate([R|Free], Allocated, From) -> link(From), From ! {resource_alloc,{yes,R}}, server(Free, [{R,From}|Allocated]); allocate([], Allocated, From) -> From ! {resource_alloc,no}, server([], Allocated). ~~~ free/4更復雜些: ~~~ free(Free, Allocated, From, R) -> case lists:member({R, From}, Allocated) of true -> From ! {resource_alloc, yes}, Allocated1 = lists:delete({R, From}, Allocated), case lists:keysearch(From, 2, Allocated1) of false -> unlink(From); _ -> true end, server([R|Free], Allocated1); false -> From ! {resource_alloc, error}, server(Free, Allocated) end. ~~~ 首先我們檢查將要被釋放的資源,的確是分配給想要釋放資源的這個進程的。如果是的話,lists:member({R,From},Allocated)返回true。我們像之前那樣建立一個新的鏈表來存放被分配出去的資源。我們不能只是簡單的unlinkFrom,而必須首先檢查Form是否持有其他資源。如果keysearch(From,2,Allocated1)(見附錄??)返回了false,From就沒有持有其他資源,這樣我們就可以unlinkFrom了。 如果一個我們與之建立了link關系的進程終止了,服務程序將會收到一個EXIT信號,然后我們調用Check(Free,Allocated,From)函數。 ~~~ check(Free, Allocated, From) -> case lists:keysearch(From, 2, Allocated) of false -> server(Free, Allocated); {value, {R, From}} -> check([R|Free], lists:delete({R, From}, Allocated), From) end. ~~~ 如果lists:keysearch(From,2,Allocated)返回了false,我們就沒有給這個進程分配過資源。如果返回了{value,{R,From}},我們就能知道資源R被分配給了這個進程,然后我們必須在繼續檢查該程序是否還持有其他資源之前,將這個資源添加到未分配資源列表,并且將他從已分配資源列表里刪除。注意這種情況下我們不需要手動的與該進程解除連接,因為當它終止的時候,連接就已經解除了。 釋放一個沒有被分配出去的資源是可能一個嚴重的錯誤。我們應當修改程序??.6中的free/1函數,以便殺死試圖這樣干的程序:[[2]](#)。 ~~~ free(Resource) -> resource_alloc ! {self(),{free,Resource}}, receive {resource_alloc, error} -> exit(bad_allocation); % exit added here {resource_alloc, Reply} -> Reply end. ~~~ 用這種方法殺死的程序,如果它還持有其他資源,同時還與服務程序保持著連接,那么服務程序因此將收到一個EXIT信號,如上面所述,處理這個信號的結果會是資源被釋放。 以上內容說明了這么幾點: - 通過設計這樣一種服務程序接口,使得客戶端通過訪問函數(這里是allocate/0和free/1)訪問服務程序,并且防止了危險的“幕后操作”。客戶端和服務程序之間的連接對用戶來說是透明的。特別是客戶端不需要知道服務程序的進程ID,因此也就不能干涉它的運行。 - 一個服務程序如果捕獲EXIT信號,并且和它的客戶端建立連接以便能監視它的話,就可以在客戶端進程死亡的時候采取適當的處理行為。 ### 分離計算部分 在一些程序里,我們可能希望將計算部分完全隔離出來,以免影響其它程序。Erlang shell就是這樣一個東西。第??章那個簡單的shell是有缺陷的。在它里面運行的一個表達式可能通過這幾種方式影響到進程: - 它可以發送進程標示符給其他進程(self/0),然后就可以與這個進程建立連接,給它發送消息。 - 它可以注冊或注銷一個進程 程序8.1用另外一種方法實現了一個shell: 程序8.1 ~~~ -module(c_shell). -export([start/0, eval/2]). start() -> process_flag(trap_exit, true), go(). go() -> eval(io:parse_exprs('-> ')), go(). eval({form, Exprs}) -> Id = spawn_link(c_shell, eval, [self(), Exprs]), receive {value, Res, _} -> io:format("Result: ~w~n", [Res]), receive {'EXIT', Id, _ } -> true end; {'EXIT', Id, Reason} -> io:format("Error: ~w!~n", [Reason]) end; eval(_) -> io:format("Syntax Error!~n", []). eval(Id, Exprs) -> Id ! eval:exprs(Exprs, []). ~~~ shell進程捕獲EXIT信號。命令在一個與shell進程連接的單獨的進程(spawn_link(c_shell,eval,[self(),Exprs]))中運行。盡管事實上我們把shell進程的進程ID給了c_shell:eval/2,但是因為對于作為實際執行者的eval:exprs/2函數,并沒有給它任何參數,因此也就不會對造成影響。 ### 保持進程存活 一些進程可能對系統來說是非常重要的。例如,在一個常規的分時系統里,常常每一個終端連接都由一個負責輸入輸出的進程來服務。如果這個進程終止了,終端也就不可用了。程序8.2通過重啟終止的進程來保持進程存活。 這個注冊為keep_alive的服務程序保有一個由{Id,Mod,Func,Args}模式元組構成的列表,這個列表包含了所有正在運行的進程的標識符、模塊、函數和參數。 它使用BIF spawn_link/3啟動這些進程,因此它也和每一個進程建立連接。然后這個服務程序就開始捕獲EXIT信號,當一個進程終止了,它就會收到一個EXIT信號。在搜索了那個由元組構成的列表之后,它就能重啟這個進程。 不過程序8.2當然也需要改進。如果從進程列表里移除一個進程是不可能的話,那么當我們試圖用一個并不存在的module:function/arity來創建進程,程序就會進入死循環。建立一個沒有這些缺陷的程序,就作為練習留給讀者來完成。 ### 討論 當進程收到了一個“原因”不是normal的信號,默認行為是終止自己,并通知與它相連接的進程(見第??節)。通過使用連接和捕捉EXIT信號建立一個分層的系統是不難的。在這個系統最頂層的進程(應用進程)并不捕獲EXIT信號。具有依賴關系的進程相互連接。底層進程(操作系統進程)捕獲EXIT并且和需要監視的應用進程(見圖8.2)建立連接。使用這種操作系統結構的例子是交換機服務器和電話應用程序,將在第??章講述,第??章是它們的文件系統。 一個因為EXIT信號導致異常的應用進程,將會把信號發送給所有跟它處在通一進程集內的進程,因此整個進程集都會被殺死。連接到該進程集內應用程序的操作系統進程也會收到EXIT信號,并且會做一些清理工作,也可能重啟進程集。 程序 8.2 ~~~ loop(Processes) -> receive {From, {new_proc, Mod, Func, Args}} -> Id = spawn_link(Mod, Func, Args), From ! {keep_alive, started}, loop([{Id, Mod, Func, Args}|Processes]); {'EXIT', Id, _} -> case lists:keysearch(Id, 1, Processes) of false -> loop(Processes); {value, {Id, Mod, Func, Args}} -> P = lists:delete({Id,Mod,Func,Args}, Processes), Id1 = spawn_link(Mod, Func, Args), loop([{Id1, Mod, Func, Args} | P]) end end. new_process(Mod, Func, Args) -> keep_alive ! {self(), {new_proc, Mod, Func, Args}}, receive {keep_alive, started} -> true end. ~~~ ![_images/8.2.png](https://box.kancloud.cn/2015-09-04_55e90af03f1be.png) 圖8.2 操作系統和應用程序進程 腳注 | [[1]](#) | 即數字0到9和*以及#。 | |-----|-----| | [[2]](#) | 這可能是一個好的編程練習,因為它將強制程序的編寫者更正這些錯誤。 | |-----|-----|
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看