2013-3-25 22:45:01更新:抱歉 抱歉 腦子里面想的是進程注冊 手誤 一直把進程注冊寫成了進程字典
Erlang 進程注冊機制
目前的限制是:

- names只能是atom
- 一個進程只能注冊一個name
- 不能進行高效的搜索和遍歷,進程信息的檢索是通過遍歷檢查進程的元數據完成的.
Gproc是Erlang進程注冊機制的加強版,提供了如下原生進程注冊沒有的功能:
- 使用任意Erlang Term作為進程的別名
- 一個進程注冊多個別名
- 支持QLC和match specification高效查詢
- 自動移交已經注冊的names和屬性到另外的進程
- .....
那它是怎么做的呢?這些額外的信息是維護在哪里,是ETS嗎?動手操練一下,驗證想法:
Eshell V5.9 (abort with ^G) 1> application:start(gproc). ok 2> ets:i(). id name type size mem owner ---------------------------------------------------------------------------- 13 code set 265 10683 code_server 4110 code_names set 53 7018 code_server 8207 shell_records ordered_set 0 73 <0.26.0> ac_tab ac_tab set 9 929 application_controller file_io_servers file_io_servers set 0 283 file_server_2 global_locks global_locks set 0 283 global_name_server global_names global_names set 0 283 global_name_server global_names_ext global_names_ext set 0 283 global_name_server global_pid_ids global_pid_ids bag 0 283 global_name_server global_pid_names global_pid_names bag 0 283 global_name_server gproc gproc ordered_set 0 73 gproc_sup gproc_monitor gproc_monitor ordered_set 0 73 gproc_monitor inet_cache inet_cache bag 0 283 inet_db inet_db inet_db set 29 554 inet_db inet_hosts_byaddr inet_hosts_byaddr bag 0 283 inet_db inet_hosts_byname inet_hosts_byname bag 0 283 inet_db inet_hosts_file_byaddr inet_hosts_file_byaddr bag 0 283 inet_db inet_hosts_file_byname inet_hosts_file_byname bag 0 283 inet_db ok
應用程序啟動之后我們查看ETS表的信息,發現多出來兩個表:gproc和gproc_monitor;下面我們就使用Shell進程完成測試,為當前的Shell進程創建別名"Shell",並向它發送消息dvd,之后再shell中flush()查看已經接收到的消息.
5> gproc:reg({p,l,"Shell"}). true 6> gproc:send({p,l,"Shell"},dvd). dvd 7> flush(). Shell got dvd ok
現在查看一下ETS表的內容,發現里面已經記錄了當前Shell進程的注冊信息;
9> ets:i(gproc). <1 > {{<0.43.0>,l}} <2 > {{<0.43.0>,{p,l,"Shell"}},[]} <3 > {{{p,l,"Shell"},<0.43.0>},<0.43.0>,undefined} EOT (q)uit (p)Digits (k)ill /Regexp -->q ok
緊接上面我們為Shell再創建一個別名,然后查看ETS
10> gproc:reg({p,l,"Shell_alia"}). true 11> ets:i(gproc). <1 > {{<0.43.0>,l}} <2 > {{<0.43.0>,{p,l,"Shell"}},[]} <3 > {{<0.43.0>,{p,l,"Shell_alia"}},[]} <4 > {{{p,l,"Shell"},<0.43.0>},<0.43.0>,undefined} <5 > {{{p,l,"Shell_alia"},<0.43.0>},<0.43.0>,undefined} EOT (q)uit (p)Digits (k)ill /Regexp -->q ok
下面簡單演示一下QLC:
40> Q5 = qlc:q([P || {{p,l,'_'},P,C} <- gproc:table( )]). {qlc_handle,{qlc_lc,#Fun<erl_eval.20.111823515>, {qlc_opt,false,false,-1,any,[],any,524288,allowed}}} 41> 41> qlc:eval(Q5). [<0.65.0>,<0.61.0>] 42> qlc:e(qlc:q([N || N <- gproc:table()])). [{{p,l,"Hello"},<0.65.0>,undefined}, {{p,l,"NEW_Process"},<0.61.0>,undefined}]
上面的幾段代碼基本上包含了它最重要的feature,我們看下實現,注冊name的過程實際上是把注冊信息寫到了ETS;而發送消息的第一步就是從ETS表中查詢name對應的Pid,然后進行發送.以發送為例看一下代碼實現:
https://github.com/esl/gproc/blob/master/src/gproc.erl %% If Key belongs to a unique object (name or aggregated counter), this %% function will send a message to the corresponding process, or fail if there %% is no such process. If Key is for a non-unique object type (counter or %% property), Msg will be send to all processes that have such an object. %% @end %% send(Key, Msg) -> ?CATCH_GPROC_ERROR(send1(Key, Msg), [Key, Msg]). send1({T,C,_} = Key, Msg) when C==l; C==g -> if T == n orelse T == a -> case ets:lookup(?TAB, {Key, T}) of [{_, Pid, _}] -> Pid ! Msg; _ -> ?THROW_GPROC_ERROR(badarg) end; T==p orelse T==c -> %% BUG - if the key part contains select wildcards, we may end up %% sending multiple messages to the same pid lists:foreach(fun(Pid) -> Pid ! Msg end, lookup_pids(Key)), Msg; true -> erlang:error(badarg) end; send1(_, _) -> ?THROW_GPROC_ERROR(badarg).
細致的實現
下面的測試我們通過gproc:info查看進程信息,下面的結果注意一下current function屬性值,是不是有疑問?
17> P2=spawn(fun() -> gproc:reg({p,l,"NEW_Process"}),receive a -> a end end ). <0.61.0> 18> ets:i(gproc). <1 > {{<0.61.0>,l}} <2 > {{<0.61.0>,{p,l,"NEW_Process"}},[]} <3 > {{{p,l,"NEW_Process"},<0.61.0>},<0.61.0>,undefined} EOT (q)uit (p)Digits (k)ill /Regexp -->q ok 19> gproc:info(P2). [{gproc,[{{p,l,"NEW_Process"},undefined}]}, {current_function,{erl_eval,receive_clauses,6}}, {initial_call,{erlang,apply,2}}, {status,waiting}, {message_queue_len,0}, {messages,[]}, {links,[]}, {dictionary,[]}, {trap_exit,false}, {error_handler,error_handler}, {priority,normal}, {group_leader,<0.25.0>}, {total_heap_size,610}, {heap_size,233}, {stack_size,8}, {reductions,100}, {garbage_collection,[{min_bin_vheap_size,46368}, {min_heap_size,233}, {fullsweep_after,65535}, {minor_gcs,1}]}, {suspending,[]}]
這里內部實現還是做得非常細致的,不是簡單的把信息附加在Process_info,比如對current function信息做了修正.
https://github.com/esl/gproc/blob/master/src/gproc.erl %% We don't want to return the internal gproc:info() function as current %% function, so we grab the 'backtrace' and extract the call stack from it, %% filtering out the functions gproc:info/_ and gproc:'-info/1-lc...' entries. %% %% This is really an indication that wrapping the process_info() BIF was a %% bad idea to begin with... :P %% info_cur_f(T, Default) -> {match, Matches} = re:run(T,<<"\\(([^\\)]+):(.+)/([0-9]+)">>, [global,{capture,[1,2,3],list}]), case lists:dropwhile(fun(["gproc","info",_]) -> true; (["gproc","'-info/1-lc" ++ _, _]) -> true; (_) -> false end, Matches) of [] -> Default; [[M,F,A]|_] -> {current_function, {to_atom(M), to_atom(F), list_to_integer(A)}} end.
好了今天就到這里.
最后小圖一張:
