在this示例中,作者通過在gen_server的init函數中執行以下操作來避免死鎖情況:在init中向self()發送消息是不是很糟糕?
self() ! {start_worker_supervisor, Sup, MFA}
。我在我的一個項目中做了類似的事情,並被告知這種方法不受歡迎,並且最好立即導致超時。什麼是可接受的模式?
在this示例中,作者通過在gen_server的init函數中執行以下操作來避免死鎖情況:在init中向self()發送消息是不是很糟糕?
self() ! {start_worker_supervisor, Sup, MFA}
。我在我的一個項目中做了類似的事情,並被告知這種方法不受歡迎,並且最好立即導致超時。什麼是可接受的模式?
考慮使用新的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
這可能是非常有效和簡單的解決方案,但我認爲這是不好的Erlang風格。 我使用定時器:apply_after,這是更好,不會與外部模塊/ gen_ *交互的印象。
我認爲最好的辦法是使用狀態機(的gen_fsm)。我們大多數gen_srvers是真正的狀態機,但是因爲初始工作努力設置get_fsm,我認爲我們最終得到了gen_srv。
最後,我會用定時器:apply_after使代碼清晰,高效的gen_fsm或者是純二郎風格(甚至更快)。
我剛纔所讀的代碼片段,但例如本身以某種方式打破 - 我不明白gen_srv操縱上司的這個結構。即使它是一些未來孩子池的管理者,這也是更重要的原因,可以明確地做到這一點,而不必指望進程的郵箱魔法。在一些更大的系統中調試這個也會很糟糕。
timer:apply_after只是一種解決方法,但真正的問題在於,如果在init中發送給self()會在設計中顯示出一個基本缺陷。 gen_fsm也不能解決這個問題,通常不會推薦通過gen_server。 – 2011-05-19 11:39:34
爲什麼fsm不能解決它?整點是執行一些東西,仍然符合init/1規範,預計它會返回狀態。所以,我們可以返回狀態並且實際執行初始狀態,執行先前的重啓。 – user425720 2011-05-19 17:04:08
@ user4:因爲狀態函數不會立即被調用,只能在調用或拋出或超時。完全像gen_server一樣。你可以使用從init返回的短暫(例如0)超時來觸發初始化,但你也可以使用gen_server來完成。 – 2011-05-20 15:30:35
呼喚自己的上司肯定似乎像一個壞主意,但我做同樣的事情所有的時間。
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,直到後我們發送的消息),和如果我需要使用其他的超時時間,它不會阻礙。
如果你的'gen_server'是用'gen_server:start_link/4'或'gen_server:start/4'啓動的,註冊發生在'Module:init/1'被調用之前。任何在其註冊名稱下尋找'gen_server'的人都可以找到它,併發送一條消息到達你自己的消息。 – r3m0t 2013-02-21 19:09:32
只是來補充已經說過拆分服務器初始化分爲兩個部分,在init/1
功能的第一和第二任一handle_cast/2
或handle_info/2
。實際上只有一個理由,那就是初始化預計需要很長時間。然後將其分開將允許gen_server:start_link
更快地返回,這對於由主管啓動的服務器來說很重要,因爲他們在啓動他們的孩子時「掛起」,並且一個緩慢啓動的孩子可以延遲整個主管啓動。
在這種情況下,我不認爲這是不好的風格分裂服務器初始化。
重要的是要小心有錯誤。 init/1
中的錯誤將導致主管在第二部分中出現錯誤時終止,因爲它們將導致主管嘗試重新啓動該子項。
我個人認爲這是更好的風格服務器發送消息給自身,無論是與一個明確!
或gen_server:cast
,與良好的描述性信息,例如init_phase_2
,它會更容易,看看到底是怎麼回事而不是更多的匿名超時。特別是如果其他地方也使用超時。
允許主管開始監控流程的好處。也許行爲應該有'second_init'回調開始? :-) – 2011-05-19 14:52:28
坦率地說,我沒有看到分裂初始化的一點。在init
做吊裝不會掛起主管,但使用timeout/handle_info
,發送消息到self()
或添加init_check
到每個處理程序(另一種可能性,雖然不是很方便)將有效掛起調用進程。那麼爲什麼我需要「工作」的主管與「不太工作」的gen_server?在初始化過程中,乾淨的實現可能應該包括對任何消息的「not_ready」回覆(爲什麼不完成從init +產生完整的初始化信息回到self()
,這會重置「not_ready」狀態),但是「未準備好」回覆應該是由調用者正確處理,這增加了很多複雜性。暫停回覆並不是一個好主意。
想象一下,一個主管啓動100個孩子,每個孩子進行半秒的網絡綁定初始化。管理員一分鐘的延遲是不好的,但客戶端可能會忍受來自gen_server的半秒延遲。 – cthulahoops 2011-05-19 23:19:31
@cthulahoops我可以想象得到(不太明白這種連接的本質是什麼,100個聽衆?),但半秒鐘不是我相信的CPU時間。那麼爲什麼不開始所有100個並行呢?這將是相同的一半。 – 2011-05-20 10:51:53
目的是並行啓動它們,但是在第一個從初始化返回之前,主管將不啓動第二個孩子。因此,即使gen_servers會在短時間內阻塞請求,init也會很快返回。 – cthulahoops 2011-05-20 14:40:13
我不會推薦將東西移動到init/1,因爲它通常是重複的代碼。當然,我們可以將它提取到內部函數,但是然後handle_call中的代碼可讀性較差。 – user425720 2011-05-19 16:58:57
好吧,如果是重複代碼,你可以把它放在一個函數中,然後從'init/1'和'handle_call/3'或'handle_cast/2'調用它。我發現函數調用比發送消息給'self()'更可讀。 – 2011-05-19 21:00:00