2011-05-19 90 views
15

this示例中,作者通過在gen_server的init函數中執行以下操作來避免死鎖情況:在init中向self()發送消息是不是很糟糕?

self() ! {start_worker_supervisor, Sup, MFA}

。我在我的一個項目中做了類似的事情,並被告知這種方法不受歡迎,並且最好立即導致超時。什麼是可接受的模式?

回答

13

更新二郎19+

考慮使用新的gen_statem行爲。此行爲支持生成FSM內部的事件:

狀態函數可以使用action()next_event插入事件,並將這樣的事件作爲下一個插入到狀態函數中。就好像它是最古老的傳入事件一樣。專用的event_type()內部可用於此類事件,使其不可能誤認爲是外部事件。

插入一個事件將取代調用自己的狀態處理功能的把戲,你經常會不得不求助於在,例如,的gen_fsm強迫他人之前處理插入的事件。

使用該模塊在action functionality,可以保證在init爲您生成的事件和之前的任何外部事件總是處理,特別是通過建立在你的init功能next_event行動。

例子:

... 

callback_mode() -> state_functions. 

init(_Args) -> 
    {ok, my_state, #data{}, [{next_event, internal, do_the_thing}]} 

my_state(internal, do_the_thing, Data) -> 
    the_thing(), 
    {keep_state, Data); 
my_state({call, From}, Call, Data) -> 
    ... 

... 

老答案

當設計一個gen_server你一般有三種不同的狀態來執行操作的選擇:

  • 啓動時,在init/1
  • 運行時,在任何handle_*函數
  • 當停止,在terminate/2

一個好的經驗法則是在處理函數在事件(打電話,發,信息等)的作用時執行的事情。在init中執行的東西不應該等待事件,這就是處理回調的用途。

因此,在這種特殊情況下,會產生一種「假」事件。我想說gen_server似乎總是想要啓動主管的起始。爲什麼不直接在init/1?是否真的需要能夠處理另一條消息(改爲在handle_info/2中進行)?該windown非常小(從開始gen_server到發送消息到self()之間的時間),所以根本不可能發生。

至於死鎖,我真的建議不要在你的init函數中調用你自己的主管。這只是不好的做法。啓動員工流程的良好設計模式將是一位頂級主管,其下有一位經理和一位工人主管。管理者通過調用職工監事開始工人:

[top_sup] 
    | \ 
    |  \ 
    |  \ 
man [work_sup] 
    /| \ 
    / | \ 
    / | \ 
    w1 ... wN 
+0

我不會推薦將東西移動到init/1,因爲它通常是重複的代碼。當然,我們可以將它提取到內部函數,但是然後handle_call中的代碼可讀性較差。 – user425720 2011-05-19 16:58:57

+1

好吧,如果是重複代碼,你可以把它放在一個函數中,然後從'init/1'和'handle_call/3'或'handle_cast/2'調用它。我發現函數調用比發送消息給'self()'更可讀。 – 2011-05-19 21:00:00

0

這可能是非常有效和簡單的解決方案,但我認爲這是不好的Erlang風格。 我使用定時器:apply_after,這是更好,不會與外部模塊/ gen_ *交互的印象。

我認爲最好的辦法是使用狀態機(的gen_fsm)。我們大多數gen_srvers是真正的狀態機,但是因爲初始工作努力設置get_fsm,我認爲我們最終得到了gen_srv。

最後,我會用定時器:apply_after使代碼清晰,高效的gen_fsm或者是純二郎風格(甚至更快)。

我剛纔所讀的代碼片段,但例如本身以某種方式打破 - 我不明白gen_srv操縱上司的這個結構。即使它是一些未來孩子池的管理者,這也是更重要的原因,可以明確地做到這一點,而不必指望進程的郵箱魔法。在一些更大的系統中調試這個也會很糟糕。

+0

timer:apply_after只是一種解決方法,但真正的問題在於,如果在init中發送給self()會在設計中顯示出一個基本缺陷。 gen_fsm也不能解決這個問題,通常不會推薦通過gen_server。 – 2011-05-19 11:39:34

+0

爲什麼fsm不能解決它?整點是執行一些東西,仍然符合init/1規範,預計它會返回狀態。所以,我們可以返回狀態並且實際執行初始狀態,執行先前的重啓。 – user425720 2011-05-19 17:04:08

+0

@ user4:因爲狀態函數不會立即被調用,只能在調用或拋出或超時。完全像gen_server一樣。你可以使用從init返回的短暫(例如0)超時來觸發初始化,但你也可以使用gen_server來完成。 – 2011-05-20 15:30:35

6

呼喚自己的上司肯定似乎像一個壞主意,但我做同樣的事情所有的時間。

init(...) -> 
    gen_server:cast(self(), startup), 
    {ok, ...}. 

handle_cast(startup, State) -> 
    slow_initialisation_task_reading_from_disk_fetching_data_from_network_etc(), 
    {noreply, State}. 

我覺得這比使用超時和handle_info更清晰,這幾乎保證沒有消息可以提前獲得啓動消息的(沒有其他人我們的PID,直到後我們發送的消息),和如果我需要使用其他的超時時間,它不會阻礙。

+3

如果你的'gen_server'是用'gen_server:start_link/4'或'gen_server:start/4'啓動的,註冊發生在'Module:init/1'被調用之前。任何在其註冊名稱下尋找'gen_server'的人都可以找到它,併發送一條消息到達你自己的消息。 – r3m0t 2013-02-21 19:09:32

7

只是來補充已經說過拆分服務器初始化分爲兩個部分,在init/1功能的第一和第二任一handle_cast/2handle_info/2。實際上只有一個理由,那就是初始化預計需要很長時間。然後將其分開將允許gen_server:start_link更快地返回,這對於由主管啓動的服務器來說很重要,因爲他們在啓動他們的孩子時「掛起」,並且一個緩慢啓動的孩子可以延遲整個主管啓動。

在這種情況下,我不認爲這是不好的風格分裂服務器初始化。

重要的是要小心有錯誤。 init/1中的錯誤將導致主管在第二部分中出現錯誤時終止,因爲它們將導致主管嘗試重新啓動該子項。

我個人認爲這是更好的風格服務器發送消息給自身,無論是與一個明確!gen_server:cast,與良好的描述性信息,例如init_phase_2,它會更容易,看看到底是怎麼回事而不是更多的匿名超時。特別是如果其他地方也使用超時。

+0

允許主管開始監控流程的好處。也許行爲應該有'second_init'回調開始? :-) – 2011-05-19 14:52:28

0

坦率地說,我沒有看到分裂初始化的一點。在init做吊裝不會掛起主管,但使用timeout/handle_info,發送消息到self()或添加init_check到每個處理程序(另一種可能性,雖然不是很方便)將有效掛起調用進程。那麼爲什麼我需要「工作」的主管與「不太工作」的gen_server?在初始化過程中,乾淨的實現可能應該包括對任何消息的「not_ready」回覆(爲什麼不完成從init +產生完整的初始化信息回到self(),這會重置「not_ready」狀態),但是「未準備好」回覆應該是由調用者正確處理,這增加了很多複雜性。暫停回覆並不是一個好主意。

+1

想象一下,一個主管啓動100個孩子,每個孩子進行半秒的網絡綁定初始化。管理員一分鐘的延遲是不好的,但客戶端可能會忍受來自gen_server的半秒延遲。 – cthulahoops 2011-05-19 23:19:31

+0

@cthulahoops我可以想象得到(不太明白這種連接的本質是什麼,100個聽衆?),但半秒鐘不是我相信的CPU時間。那麼爲什麼不開始所有100個並行呢?這將是相同的一半。 – 2011-05-20 10:51:53

+0

目的是並行啓動它們,但是在第一個從初始化返回之前,主管將不啓動第二個孩子。因此,即使gen_servers會在短時間內阻塞請求,init也會很快返回。 – cthulahoops 2011-05-20 14:40:13

相關問題