概要
勉強するために、非同期で並列HTTP通信して10のURLからダウンロードするプログラムを書いて、実行速度を比較検証します。最後にErlang有識者にコード見せたら、かなり厳しめの指摘を頂いたのでその点について最期に書いてます。
ダウンロード機能のみ実装
HTTP通信してファイルに保存する
http_downloader_2
%%%-------------------------------------------------------------------
%%% @doc
%%% http通信
%%% 参考: http://erlang.org/doc/apps/inets/http_client.html
%%% @end
%%% Created : 26. 5 2016 15:54
%%%-------------------------------------------------------------------
-module(http_downloader_2).
%% API
-export([main/0]).
%% main
main() ->
Url = "http://www.erlang.org",
Body = http_request(Url),
file_writer(Body, "/tmp/download/output.dat"),
ok.
%% printer
print(Message) -> io:format("E: ~p~n",[Message]).
%% http 通信してBodyを返す
http_request(URL) ->
inets:start(),
{ok, {{Version, 200, ReasonPhrase}, Headers, Body}} = httpc:request(URL),
Body.
%% fileに保存する
file_writer(Body, FilePath) ->
file:write_file(FilePath, Body, [binary, write]).
実行
>>> erlc ./http_downloader_2.erl
>>> erl -noshell -s http_downloader_2 main -s init stop
>>> ls -lta /tmp/download/output.dat
-rw-r--r-- 1 ****** wheel 12740 5 27 16:55 /tmp/download/output.dat
プロセス1つで順番にダウンロードする(同期して実行)
プロセスを1つ生成(spawn)して順番にダウンロードするモデル
http_downloader_4
%%%-------------------------------------------------------------------
%%% @doc
%%% プロセス1つで順番にダウンロードする
%%%
%%% @end
%%% Created : 26. 5 2016 15:54
%%%-------------------------------------------------------------------
-module(http_downloader_4).
%% API
-export([main/0, download_process/0]).
%% main
main() ->
Urls = ["http://erlang.org/doc/man/httpc.html",
"http://erlang.org/doc/apps/inets/http_client.html",
"http://erlang.org/doc/apps/inets/inets_services.html#id60923",
"http://erlang.org/doc/apps/inets/release_notes.html",
"http://erlang.2086793.n4.nabble.com/Question-re-io-format-and-string-handling-td2109722.html",
"http://www.erlang.org/mailman/listinfo/erlang-questions",
"http://stackoverflow.com/questions/7595128/how-to-optimize-the-receive-loop-for-thousands-of-messages-in-erlang",
"http://stackoverflow.com/jobs",
"http://stackoverflow.com/tags",
"http://stackoverflow.com/questions/tagged/c%23"],
%% downloader プロセスを生成する
Pid2 = spawn(?MODULE, download_process, []),
Receiver = fun(Url) ->
%% プロセスにメッセージを投げる
Pid2 ! {self(), Url},
%% プロセスからの応答を待つ
receive
{Pid2, Msg} ->
print("Download Finish: " ++ Msg)
end
end,
lists:foreach(Receiver, Urls),
%% プロセスの停止
Pid2 ! stop.
%% printer
print(Message) -> io:format("E: ~p~n",[Message]).
%% download用のプロセス
download_process() ->
receive
{From, Msg} ->
%% download処理
downloader(Msg),
%% 応答を返す
From ! {self(), Msg},
download_process();
stop ->
true
end.
%% http通信してfileに保存する
downloader(Url) ->
Body = http_request(Url),
Path = path_generator(Url),
file_writer(Body, Path),
ok.
%% URLからファイルパスを生成する。
path_generator(Url) ->
FileName = name_generator(Url),
Path = string:concat("/tmp/download/", FileName),
Path.
%% URLからファイル名を生成する。
name_generator(Url) ->
{ok, Result} = http_uri:parse(Url),
{Scheme, UserInfo, Host, Port, Path, Query} = Result,
%% "/" を削除して文字列に変換
re:replace(Path, "/", "", [global, {return, list}]).
%% http 通信してBodyを返す
http_request(URL) ->
inets:start(),
{ok, {{Version, 200, ReasonPhrase}, Headers, Body}} = httpc:request(URL),
Body.
%% fileに保存する
file_writer(Body, FilePath) ->
file:write_file(FilePath, Body, [binary, write]).
実行
rm -rf /tmp/download/*
erlc ./http_downloader_4.erl
echo "+++++++++++++"
erl -noshell -s http_downloader_4 main -s init stop
echo "+++++++++++++"
ls -lta /tmp/download/
実行結果
E: "Download Finish: http://erlang.org/doc/man/httpc.html"
E: "Download Finish: http://erlang.org/doc/apps/inets/http_client.html"
E: "Download Finish: http://erlang.org/doc/apps/inets/inets_services.html#id60923"
E: "Download Finish: http://erlang.org/doc/apps/inets/release_notes.html"
E: "Download Finish: http://erlang.2086793.n4.nabble.com/Question-re-io-format-and-string-handling-td2109722.html"
E: "Download Finish: http://www.erlang.org/mailman/listinfo/erlang-questions"
E: "Download Finish: http://stackoverflow.com/questions/7595128/how-to-optimize-the-receive-loop-for-thousands-of-messages-in-erlang"
E: "Download Finish: http://stackoverflow.com/jobs"
E: "Download Finish: http://stackoverflow.com/tags"
E: "Download Finish: http://stackoverflow.com/questions/tagged/c%23"
+++++++++++++
total 1104
drwxr-xr-x 12 **** wheel 408 5 27 18:00 .
-rw-r--r-- 1 **** wheel 95746 5 27 18:00 questionstaggedc%23
-rw-r--r-- 1 **** wheel 85691 5 27 18:00 jobs
-rw-r--r-- 1 **** wheel 91712 5 27 18:00 questions7595128how-to-optimize-the-receive-loop-for-thousands-of-messages-in-erlang
-rw-r--r-- 1 **** wheel 67743 5 27 18:00 tags
-rw-r--r-- 1 **** wheel 6266 5 27 18:00 mailmanlistinfoerlang-questions
-rw-r--r-- 1 **** wheel 117721 5 27 18:00 Question-re-io-format-and-string-handling-td2109722.html
-rw-r--r-- 1 **** wheel 9012 5 27 18:00 docappsinetshttp_client.html
-rw-r--r-- 1 **** wheel 6574 5 27 18:00 docappsinetsinets_services.html
-rw-r--r-- 1 **** wheel 7011 5 27 18:00 docappsinetsrelease_notes.html
-rw-r--r-- 1 **** wheel 57479 5 27 18:00 docmanhttpc.html
drwxrwxrwt 28 root wheel 952 5 27 16:24 ..
複数プロセスで非同期ダウンロード
dwn.erl
%%%-------------------------------------------------------------------
%%% @author hami
%%% @copyright (C) 2016, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 30. 5 2016 12:36
%%%-------------------------------------------------------------------
-module(dwn).
-author("hami").
%% API
-export([main/0, download/1]).
main() ->
Urls = ["http://erlang.org/doc/man/httpc.html",
"http://erlang.org/doc/apps/inets/http_client.html",
"http://erlang.org/doc/apps/inets/inets_services.html#id60923",
"http://erlang.org/doc/apps/inets/release_notes.html",
"http://erlang.2086793.n4.nabble.com/Question-re-io-format-and-string-handling-td2109722.html",
"http://www.erlang.org/mailman/listinfo/erlang-questions",
"http://stackoverflow.com/questions/7595128/how-to-optimize-the-receive-loop-for-thousands-of-messages-in-erlang",
"http://stackoverflow.com/jobs",
"http://stackoverflow.com/tags",
"http://stackoverflow.com/questions/tagged/c%23"],
%% マルチプロセスでダウンロード開始
downloader(self(), Urls),
%% ダウンロード完了を受信する
receiver_download_finish(length(Urls)),
ok.
%% ダウンロードプロセスから受信する
receiver_download_finish(0) -> ok;
receiver_download_finish(N) when N > 0 ->
receive
{Msg} ->
print(Msg),
receiver_download_finish(N - 1);
stop ->
true
end.
%% 非同期でダウンロードプロセスを立ち上げる
downloader(MainPid, Urls) ->
Starter = fun(X) ->
%% プロセスの生成
Pid2 = spawn(?MODULE, download, [MainPid]),
%% プロセスにメッセージを投げる
Pid2 ! {X} end,
lists:foreach(Starter, Urls),
ok.
%% 立ち上げるプロセス
download(MainPid) ->
receive
{Url} ->
%% ダウンロード実行
download_and_write_file(Url),
%% 完了した旨を通知する
MainPid ! {Url};
stop ->
true
end.
%% printer
print(Message) -> io:format("E: ~p~n",[Message]).
%% http通信してfileに保存する
download_and_write_file(Url) ->
Body = http_request(Url),
Path = path_generator(Url),
file_writer(Body, Path),
ok.
%% URLからファイルパスを生成する。
path_generator(Url) ->
FileName = name_generator(Url),
Path = string:concat("/tmp/download/", FileName),
Path.
%% URLからファイル名を生成する。
name_generator(Url) ->
{ok, Result} = http_uri:parse(Url),
{Scheme, UserInfo, Host, Port, Path, Query} = Result,
%% "/" を削除して文字列に変換
re:replace(Path, "/", "", [global, {return, list}]).
%% http 通信してBodyを返す
http_request(URL) ->
inets:start(),
{ok, {{Version, 200, ReasonPhrase}, Headers, Body}} = httpc:request(URL),
Body.
%% fileに保存する
file_writer(Body, FilePath) ->
file:write_file(FilePath, Body, [binary, write]).
コンパイルと実行
# コンパイルと実行
echo "+++++++++++++"
erlc ./dwn.erl
echo "+++++++++++++"
time erl -noshell -s dwn main -s init stop
echo "+++++++++++++"
実行結果
+++++++++++++
E: "http://stackoverflow.com/questions/7595128/how-to-optimize-the-receive-loop-for-thousands-of-messages-in-erlang"
E: "http://stackoverflow.com/tags"
E: "http://stackoverflow.com/questions/tagged/c%23"
E: "http://stackoverflow.com/jobs"
E: "http://erlang.2086793.n4.nabble.com/Question-re-io-format-and-string-handling-td2109722.html"
E: "http://erlang.org/doc/apps/inets/http_client.html"
E: "http://erlang.org/doc/apps/inets/release_notes.html"
E: "http://erlang.org/doc/apps/inets/inets_services.html#id60923"
E: "http://erlang.org/doc/man/httpc.html"
E: "http://www.erlang.org/mailman/listinfo/erlang-questions"
real 0m3.462s
user 0m0.258s
sys 0m0.142s
+++++++++++++
```shell-session:実行結果
Erlang有識者にコード見せた結果
ロジックは正しいが、業務では次の3つの関数は基本的に利用せず、behaviorを利用するとアドバイスを頂きました。
■ 業務でErlang書くときは、次の3つの関数をほぼ利用しない
- spawn 禁止
- receive 禁止
- Pid ! {} 禁止
■ 対策
適切にbehaviorを使って書き直そう
- application
- supervisor
- gen_server
- gen_fsm
次の学習
次はbehaviorを使って並行ダウンロードのプログラム書き直してみます

