ProducerConsumer再び

30分プログラム、その432。前にやったProducerConsumerをもう一度やってみる。
前はJavaの実装を素直にErlangに移植しただけだった。でも、Channelがウェイトセットを持っているのがダサい。
じゃあ、なんでウェイトセットが必要かというと、ProducerやConsumerのスレッドを使い回すのが目的な気がする。Javaはスレッドの生成が重いから、それを避けたいんだと想像してる。
ということは、プロセス生成が軽いErlangじゃあウェイトセットはいらないんじゃないか、と思ったので作ってみた。ProducerがDataを作るたびに、Consumerを生成するようにしたら、ChannelにDataを貯める必要がなくなった。やったね。というか、これはThread-per-messageと呼ぶような気がちょっとする。

使い方

$ erl -noshell -s prodcons start -s init stop
...
                B put Cake 102
                A put Cake 103
                C put Cake 104
Eat Cake 104
Eat Cake 103
Eat Cake 102
                C put Cake 105
                A put Cake 106
                B put Cake 107
Eat Cake 107
Eat Cake 106
Eat Cake 105
                B put Cake 108
                A put Cake 109
                C put Cake 110
                C put Cake 111
                A put Cake 112
                B put Cake 113
                B put Cake 114
                A put Cake 115
                C put Cake 116
Eat Cake 110
Eat Cake 109
Eat Cake 108

ソースコード

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

some_work() ->
    timer:sleep(random:uniform(1000)).

%%% number server
number_server(No) ->
    receive
	{get,From} ->
	    From ! No,
	    number_server(No+1)
    end.

number_server() ->
    Pid = spawn(fun () -> number_server(0) end),
    register(number,Pid).

get_no() ->
    Self = self(),
    number ! {get,Self},
    receive
	N -> N
    end.

%%% eater
eater(Cake)->
    some_work(),
    io:format("Eat ~s~n",[Cake]).

%%% cook
cook(Table,Name) ->
    some_work(),
    Cake = io_lib:format("Cake ~p",[get_no()]),
    io:format("\t\t~s put ~s~n",[Name,Cake]),
    Table ! {put,Cake},
    cook(Table,Name).

%%% table
table(Eater_fun)->
    receive
	{put,Cake} ->
	    spawn(fun ()-> Eater_fun(Cake) end),
	    table(Eater_fun)
    end.

%%% start
start() ->
    number_server(),
    Table = spawn(fun () -> table(fun prodcons:eater/1) end),
    spawn(fun () -> cook(Table,"A") end),
    spawn(fun () -> cook(Table,"B") end),
    cook(Table,"C").