並列ダウンローダっぽい何か

30分プログラム、その672。サーバごとにスレッドを立ち上げて、並列でダウンロードするダウンローダを目指してみた。実際は、スレッドを立ち上げるとこまでしかやっていない。
Webからファイルをダウンロードするときは、なるべく並列化して効率をあげたい。でも、一個のサーバから同時に落しすぎると怒られちゃう。
というわけで、サーバごとにスレッドを立ち上げて、同じサーバからは一個しかダウンロードしてないけど、全体で見ると並列で落すダウンローダを目指してみた。
実際は、時間がなくてスレッドを管理する部分のコードしか書いてない。

使い方

example() ->
    % URLリスト。
    % {<サーバ名>,<URL>}のようになっている。
    Data = [{a,"http://example.com/"},
	    {a,"http://example.co.jp/"},
	    {b,"http://google.com"}],
    % ダウンロード開始
    downloader:start(Data).

ソースコード

-module(downloader).
-compile([export_all]).

% ------------------------------
% download thread
% ------------------------------
loop() ->
    receive
	{url,Url} ->
	    io:format("Now downloading: ~p~n",[Url]),
	    timer:sleep(1000),
	    loop();
	{close,From} ->
	    From ! bye
    end.

download() ->
    spawn(fun loop/0).

% ------------------------------
% manage thread
% ------------------------------
get_pid(Table,Key)->
    case ets:lookup(Table,Key) of
	[] ->
	    Pid = download(),
	    ets:insert(Table,{Key,Pid}),
	    Pid;
	[{Key,Pid}] ->
	    Pid
    end.

start(_,[]) ->
    ok;
start(Table,[{Key,Url}|Xs]) ->
    get_pid(Table,Key) ! {url,Url},
    start(Table,Xs).

waits_for(0) -> bye;
waits_for(N) ->
    receive
      bye -> waits_for(N-1)
    end.

waits(Pids) ->
    lists:foreach(fun(Pid) -> Pid ! {close,self()} end, Pids),
    waits_for(length(Pids)).

start(Xs)->
    Table = ets:new(table,[set]),
    start(Table,Xs),
    Pids = lists:map(fun({_Key,Pid})->Pid end, ets:tab2list(Table)),
    waits(Pids),
    ets:delete(Table).

% ------------------------------
% example
% ------------------------------
example() ->
    Data = [{a,"http://example.com/"},
	    {a,"http://example.co.jp/"},
	    {b,"http://google.com"}],
    start(Data).