Erlang FizzBuzzをメッセージパッシングで書く

# メッセージパッシングの基礎
spawnで軽量スレッドを呼び出す。マルチスレッド対応することでスケールするFizzBuzzが出来るはず...はず

```erlang:non-mp
main() ->
  printer("test message").
printer(M) ->
  io:format("M: ~p~n",[M]).
```


```erlang:mp
main() ->
  Pid = spawn(fun() -> printer() end),
  Pid ! "test message".

printer() ->
  receive
    M -> io:format("M: ~p~n",[M])
  end. 
```

# まずは普通にFizzBuzz書く
```erlang
main() ->
  lists:foreach(fun(X) -> printer(fizzbuzz(X)) end, lists:seq(1, 100)).

fizzbuzz(X) ->
  case X of
    X when X rem 15 =:= 0 -> fizzbuzz;
    X when X rem 5 =:= 0 -> buzz;
    X when X rem 3 =:= 0 -> fizz;
    X -> X
  end.

printer(M) ->
  io:format("~p~n",[M]).
```

# メッセージパッシングで書く
```erlang
main() ->
  Pid = spawn(fun() -> queue() end),
  lists:foreach(fun(X) -> Pid ! X end, lists:seq(1, 100)).

queue() ->
  receive
    M -> printer(fizzbuzz(M))
  end,
  queue(). %% プロセスが途中終了しないように末尾再帰

fizzbuzz(X) ->
  timer:sleep(100),  %% fizzbuzzが重い処理だと仮定
  case X of
    X when X rem 15 =:= 0 -> fizzbuzz;
    X when X rem 5 =:= 0 -> buzz;
    X when X rem 3 =:= 0 -> fizz;
    X -> X
  end.

printer(M) ->
  io:format("~p~n",[M]).
```

# メッセージパッシングで書くことの副作用
この処理の問題点は、別プロセスでfizzbuzzを計算しているため
fizzbuzz処理が重い処理だったり、計算回数が多い場合
メインプロセスが計算終了前に終了してしまい、最後まで結果を受け取れないこと。
実際にtimer:sleep(100)を入れると、途中で切れる。
解決方法として、すべての計算が終了するまでメインプロセスに待機処理を追加すると解消する

# 同期して終了まで待つ
```erlang
main() ->
  printer("Start"),
  Pid = spawn(fun() -> queue() end),
  PidMain = self(),
  Count = 100,
  lists:foreach(fun(X) -> Pid ! {X, PidMain } end, lists:seq(1, Count)),
  join(Count),  %% queueから終了メッセージを受け取る
  printer("Finish").

queue() ->
  receive
    {M, PidMain} ->
      PidMain ! fizzbuzz(M)  %% メインプロセスにメッセージ送信
  end,
  queue(). %% プロセスが途中終了しないように末尾再帰

fizzbuzz(X) ->
  timer:sleep(100),  %% fizzbuzzが重い処理だと仮定
  case X of
    X when X rem 15 =:= 0 -> fizzbuzz;
    X when X rem 5 =:= 0 -> buzz;
    X when X rem 3 =:= 0 -> fizz;
    X -> X
  end.

printer(M) ->
  io:format("~p~n",[M]).

join(0) -> ok;
join(Count) ->
  receive
    M ->
      printer(M),
      join(Count - 1)
  end.
```

# 並列度を上げる
『同期して終了まで待つver』はspawnを1回のみしか呼ばないため、並列度1のため
cpuを上手に使えていないため、fizzbuzz計算するたびにspawnすることで
並列度を上げる。spawnは軽量スレッドでオーバーヘッドは、ほぼ無視できる。
またメモリも4kbくらいしか使わないらしい

```erlang
main() ->
  printer("Start"),
%%   Pid = spawn(fun() -> queue() end),
  PidMain = self(),
  Count = 100,
%%   lists:foreach(fun(X) -> Pid ! {X, PidMain } end, lists:seq(1, Count)),
  lists:foreach(fun(X) -> spawn(fun() -> queue() end) ! {X, PidMain } end, lists:seq(1, Count)),
  join(Count),  %% queueから終了メッセージを受け取る
  printer("Finish").

queue() ->
  receive
    {M, PidMain} ->
      PidMain ! fizzbuzz(M) %% メインプロセスにメッセージ送信
  end,
  queue(). %% プロセスが途中終了しないように末尾再帰

fizzbuzz(X) ->
  timer:sleep(100),  %% fizzbuzzが重い処理だと仮定
  case X of
    X when X rem 15 =:= 0 -> fizzbuzz;
    X when X rem 5 =:= 0 -> buzz;
    X when X rem 3 =:= 0 -> fizz;
    X -> X
  end.

printer(M) ->
  io:format("~p~n",[M]).

join(0) -> ok;
join(Count) ->
  receive
    M ->
      printer(M),
      join(Count - 1)
  end.

```

# 並列度を上げることの副作用
並列計算した結果、計算速度はちょっ早になったが、順番バラバラで結果が届く。どうしてこうなった...

![スクリーンショット 2015-09-29 13.28.04.png](https://qiita-image-store.s3.amazonaws.com/0/65312/d67649a0-9c97-97b7-6004-51b73d97e952.png)